What's the deal with vertical-align: baseline? - css

I thought I knew my way around CSS, but I needed to explain something to someone just now and I found I couldn't.
My question basically boils down to: why is vertical-align:baseline ignored when there are other alignments in the same line?
Example: if the second span has vertical-align:bottom, the first span's vertical alignment is ignored if it is baseline; it behaves as if it has bottom too.
span:first-child {vertical-align:baseline}
span:last-child {font-size:3em; vertical-align:bottom;}
<p>
<span>one</span> <span>two</span>
</p>
While if all the spans have a vertical-align other than baseline, or, if they are all baseline, then they behave as expected.
span:first-child {vertical-align:top}
span:last-child {font-size:3em; vertical-align:bottom;}
<p>
<span>one</span> <span>two</span>
</p>
span:first-child {vertical-align:baseline}
span:last-child {font-size:3em; vertical-align:baseline;}
<p>
<span>one</span> <span>two</span>
</p>
If this is normal behaviour, then why isn't it described anywhere? I haven't found any source that says baseline and top/bottom interfere with each other in such a way.

Vertical-Align
vertical-align is used to align inline-level elements. These are elements, whose display property evaluates to:
inline
inline-block
inline-table (not considered in this answer)
Inline-level elements are laid out next to each other in lines. Once there are more elements than fit into the current line, a new line is created beneath it. All these lines have a so-called line box, which encloses all the content of its line. Differently sized content means line boxes of different height.
In the following illustration the top and bottom of line boxes are indicated by red lines.
Inside these line boxes the property vertical-align is responsible for aligning the individual elements.
Baseline
The most important reference point to align vertically is the baseline of the involved elements. In some cases the top and bottom edge of the element’s enclosing box becomes important, too.
Inline elements
The top and bottom edge of the line height is indicated by red lines, the height of the font by green lines and the baseline by a blue line.
On the left, the text has a line height set to the same height as the font-size. The green and red line collapsed to one line on each side.
In the middle, the line height is twice as large as the font-size.
On the right, the line height is half as large as the font-size.
Note that the inline element’s outer edges (the red lines) does not matter, if the line height is smaller than the height of the font.
Inline-Block Element
From left to right you see:
an inline-block element with in-flow content
an inline-block element with in-flow content and overflow: hidden
an inline-block element with no in-flow content (but the content area has a height)
The boundaries of the margin is indicated by red lines, the border is yellow, the padding green and the content area blue. The baseline of each inline-block element is shown as a blue line.
The inline-block element’s baseline depends on whether the element has in-flow content. In case of:
in-flow content the baseline of the inline-block element is the baseline of the last content element in normal flow (example on the left)
in-flow content but an overflow property evaluating to something other than visible, the baseline is the bottom edge of the margin-box (example in the middle)
no in-flow content the baseline is, again, the bottom edge of the margin-box (example on the right)
Line box
This is probably the most confusing part, when working with vertical-align. It means, the baseline is placed where ever it needs to be to fulfil all other conditions like vertical-align and minimizing the line box’s height. It is a free parameter in the equation.
Since the line box’s baseline is invisible, it may not immediately be obvious where it is. But, you can make it visible very easily. Just add a character at the beginning of the line in questions, like the "x" in the figure. If this character is not aligned in any way, it will sit on the baseline by default.
Around its baseline the line box has what we might call its text box (green lines in the figure). The text box can simply be thought of as an inline element inside the line box without any alignment. Its height is equal to the font-size of its parent element. Therefore, the text box only just encloses the unformatted text of the line box. Since this text box is tied to the baseline, it moves when the baseline moves.
Snippet
If you want to do some experiment with various vertical-align and font-size here you have a snippet where you can try it out. Is also available in JSFiddle.
let sl1 = document.getElementById('sl1');
let sl2 = document.getElementById('sl2');
let sl3 = document.getElementById('sl3');
let sl4 = document.getElementById('sl4');
let elm1 = document.getElementById('elm1');
let elm2 = document.getElementById('elm2');
let elm3 = document.getElementById('elm3');
let elm4 = document.getElementById('elm4');
let ip1 = document.getElementById('ip1');
let ip2 = document.getElementById('ip2');
let ip3 = document.getElementById('ip3');
let ip4 = document.getElementById('ip4');
let slArr = [sl1, sl2, sl3, sl4];
let elmArr = [elm1, elm2, elm3, elm4];
let ipArr = [ip1, ip2, ip3, ip4];
let valueArr = ['baseline', 'top', 'middle', 'bottom'];
for (let i = 0; i < slArr.length; i++) {
slArr[i].addEventListener('change', (event) => {
elmArr[i].style.verticalAlign = event.target.value;
elmArr[i].innerHTML = event.target.value;
addDiv();
})
}
for (let i = 0; i < ipArr.length; i++) {
ipArr[i].addEventListener('change', (event) => {
elmArr[i].style.fontSize = event.target.value + 'em';
addDiv();
})
}
document.getElementById('btnRandom').addEventListener('click', () => {
for (let i = 0; i < elmArr.length; i++) {
let element = elmArr[i];
let fontSize = Math.floor(Math.random() * 4 + 1);
ipArr[i].value = fontSize;
element.style.fontSize = fontSize + 'em';
let styleIndex = Math.floor(Math.random() * 4);
element.style.verticalAlign = valueArr[styleIndex];
element.innerHTML = valueArr[styleIndex];
slArr[i].selectedIndex = styleIndex;
}
}, this);
function addDiv() {
let view = document.getElementById('viewer');
view.innerHTML = "";
elmArr.forEach(function(element) {
let div = document.createElement('div');
div.appendChild(element.cloneNode());
view.appendChild(div);
}, this);
}
.header span {
color: #000;
}
select {
width: 100px;
}
#elms {
border: solid 1px #000;
margin-top: 20px;
position: relative;
}
span {
color: #FFF;
font-size: 1em;
}
#elm1 {
background-color: #300;
}
#elm2 {
background-color: #6B0;
}
#elm3 {
background-color: #90A;
}
#elm4 {
background-color: #B00;
}
div {
z-index: -1;
}
#div1 {
width: 100%;
height: 1px;
background-color: #000;
position: absolute;
left: 0;
top: 25%;
}
#div2 {
width: 100%;
height: 1px;
background-color: #000;
position: absolute;
left: 0;
top: 50%;
}
#div3 {
width: 100%;
height: 1px;
background-color: #000;
position: absolute;
left: 0;
top: 75%;
}
<div class="header"> <span style="width: 100px;display: block;float: left;margin-right: 20px;">vertical align</span> <span>font-size(em)</span> </div>
<div>
<select name="sl1" id="sl1">
<option value="baseline">baseline</option>
<option value="top">top</option>
<option value="middle">middle</option>
<option value="bottom">bottom</option>
</select>
<input type="number" value="1" id="ip1" />
<br>
<select name="sl2" id="sl2">
<option value="baseline">baseline</option>
<option value="top">top</option>
<option value="middle">middle</option>
<option value="bottom">bottom</option>
</select>
<input type="number" value="1" id="ip2" />
<br>
<select name="sl3" id="sl3">
<option value="baseline">baseline</option>
<option value="top">top</option>
<option value="middle">middle</option>
<option value="bottom">bottom</option>
</select>
<input type="number" value="1" id="ip3" />
<br>
<select name="sl4" id="sl4">
<option value="baseline">baseline</option>
<option value="top">top</option>
<option value="middle">middle</option>
<option value="bottom">bottom</option>
</select>
<input type="number" value="1" id="ip4" />
<br>
<button id="btnRandom" (onclick)="random()">Random</button>
</div>
<div id="elms">
<span id="elm1">one</span>
<span id="elm2">two</span>
<span id="elm3">three</span>
<span id="elm4">four</span>
<div id="div1"></div>
<div id="div2"></div>
<div id="div3"></div>
</div>
<div id="viewer"></div>
This snippet is made by Duannx.
Source: Please note that this is an extract of Vertical-Align: All You Need To Know written by Christopher Aue.

The question is why are vertical-align: baseline; ignored. Well I don't think it is. In your first snippet you're using baseline and bottom what clearly gives a difference between the two <span> elements. So what does baseline do? Well baseline Aligns the baseline of the element with the baseline of the parent element. In the example below I've copied and adjusted some parts to give the difference.
span.one {vertical-align:baseline}
span.two {vertical-align:middle;}
<p>
<span class="one">one</span> <span class="two">two</span>
</p>
As you can see the baseline alignment acts normal just as the middle alignment.
Now lets test something else. lets swap the alignment of baseline and middle and edit middle and add a third <span>
span.one { vertical-align: top;}
span.two { vertical-align: baseline;}
span.three {vertical-align: middle; height: 20px; }
p {
height: 50px;
background-color: grey;
}
<p>
<span class="one">one</span> <span class="two">two</span> <span class="two">two</span><br><br> <span class="three">three</span>
</p>
Now if you'll edit the second snippet and take a look at the vertical-align of <span class="three"> you can clearly see that when changing the alignment the text does change it's position.
But you're question relates on text on the same line so lets take a look at the snippet down below.
span.one { vertical-align: middle;}
span.two { vertical-align: baseline;}
span.three {vertical-align: middle; height: 20px; }
p {
height: 50px;
background-color: grey;
}
<p>
<span class="one">one</span> <span class="two">two</span> <span class="two">two</span><span class="three">three</span>
</p>
As you can see in this third snippet I've placed the third <span> next to one and two. And it does make a difference. The baseline in this case is different from the middle alignment. This is because the baseline grabs the baseline of the parent. since the parent's height is normal it doesn't affect baseline and top alignment. but when you'll use middle or sub it is clearly a difference.
For information about each alignment, check out this link.

A shorter explanation: A starting point is knowing where the baseline of the parent is. Add some text within the <p> tag or a <span> with vertical-alignment set to baseline (span "one" below). The baseline of span "one" is the bottom of the characters, e.g. the bottom of the letter 'n'.
Then its easy to see how the other spans change in relation to it. I added a border around span "one" so we can see its top and bottom edges clearly.
span { }
span:nth-child(1) {vertical-align:baseline; font-size:3em; border: 1px solid gray}
span:nth-child(2) {vertical-align:top; color:red}
span:nth-child(3) {vertical-align:middle; color:green;}
span:nth-child(4) {vertical-align:bottom; color:blue;}
<p>
<span>one</span> <span>two</span> <span>three</span> <span>four</span>
</p>

Related

Why is the descender “created” when baseline is set to vertical-align?

I know that the img element has a descender margin below it by default.
.box {
border: 1px solid;
}
<div class="box">
<img src="http://placehold.jp/150x150.png"> xyz
</div>
Question
I thought that the vertical-align property "aligns" elements at a specific position, so the height doesn't change. However, with the following code, when measuring the height when changing vertical-align, height was increased only forbaseline.
Why does it behave like "making a descender only when baseline"? Isn't a descender always present regardless of the value of the vertical-align property? There was no mention of this behavior in the vertical-align specification.
var align = ["top", "bottom", "baseline"];
var idx = 0;
function changeVerticalAlign() {
document.querySelector(".box>img").style.verticalAlign = align[idx];
document.querySelector("p").innerText = "height=" + getComputedStyle(document.querySelector(".box")).getPropertyValue("height") + ", vertical-align=" + getComputedStyle(document.querySelector(".box>img")).getPropertyValue("vertical-align");
idx < 2 ? ++idx : idx = 0;
}
document.querySelector("button").addEventListener("DOMContentLoaded", changeVerticalAlign);
document.querySelector("button").addEventListener("click", changeVerticalAlign);
.box {
border: 1px solid;
}
<div class="box">
<img src="http://placehold.jp/150x150.png"> xyz
</div>
<p></p>
<button>change vertical-align to "bottom"</button>
I thought that the vertical-align property "aligns" elements at a specific position, so the height doesn't change.
This isn't true because the height is defined after the alignment is done and you can clearly notice this if you use text instead of image:
.box{
border:1px solid;
margin:5px;
}
<div class="box">
<span>some text</span>
<span>some text</span>
<span>some text</span>
</div>
<div class="box">
<span style="vertical-align:super">some text</span>
<span>some text</span>
<span>some text</span>
</div>
<div class="box">
<span style="vertical-align:super">some text</span>
<span>some text</span>
<span style="vertical-align:sub">some text</span>
</div>
From the specification:
The boxes may be aligned vertically in different ways: their bottoms or tops may be aligned, or the baselines of text within them may be aligned. The rectangular area that contains the boxes that form a line is called a line box.
and also
A line box is always tall enough for all of the boxes it contains. However, it may be taller than the tallest box it contains (if, for example, boxes are aligned so that baselines line up). When the height of a box B is less than the height of the line box containing it, the vertical alignment of B within the line box is determined by the 'vertical-align' property.
Then in another part of the specification
As described in the section on inline formatting contexts, user agents flow inline-level boxes into a vertical stack of line boxes. The height of a line box is determined as follows:
The height of each inline-level box in the line box is calculated. ....
The inline-level boxes are aligned vertically according to their 'vertical-align' property. ....
The line box height is the distance between the uppermost box top and the lowermost box bottom. (This includes the strut, as explained under 'line-height' below.)
So basically the height is defined bigger enough to hold the content after being aligned that's why the height may increase for some alignment.
If you add border to your elements you will notice the uppermost box top and the lowermost box bottom.
var align = ["top", "bottom", "baseline","super","sub","text-bottom","text-top"];
var idx = 0;
function changeVerticalAlign() {
document.querySelector(".box>img").style.verticalAlign = align[idx];
document.querySelector("p").innerText = "height=" + getComputedStyle(document.querySelector(".box")).getPropertyValue("height") + ", vertical-align=" + getComputedStyle(document.querySelector(".box>img")).getPropertyValue("vertical-align");
idx < 6 ? ++idx : idx = 0;
}
document.querySelector("button").addEventListener("DOMContentLoaded", changeVerticalAlign);
document.querySelector("button").addEventListener("click", changeVerticalAlign);
.box {
border: 1px solid;
}
.box> * {
border:1px solid red;
}
p {
margin:0;
}
<div class="box">
<img src="http://placehold.jp/150x130.png"> <span>xyz</span>
</div>
<p></p>
<button>change vertical-align</button>

web page rendering mystery: button too wide

I have a document that contains HTML and CSS.
It is being rendered in a way that I don't understand.
I have observed this with 3 different browsers
(Firefox, Chromium, and Opera),
so it is unlikely to be a browser bug.
The following image shows what I expect to see.
(Before taking this screenshot, I used my
browser's zoom function to zoom in.)
The following image shows what I actually see.
(Again, I zoomed in before capturing this image.)
The upper group of buttons is as I expect.
However,
in the lower group of buttons,
the "y" button is wider than I expect.
I'll show the markup of the document in a moment,
but first I want to talk about it.
The difference between the two "y" buttons
is that the upper one has the
class button-wider-1,
whereas the lower one has the class button-wider-2.
In the CSS, each of these classes sets a width,
using a calc expression.
The difference between the two expressions
is that the expression for button-wider-2
adds the width of an "x" button,
which means that I expect
the right-hand border of the "y" button to
align with the
right-hand border of the right-hand "x" button.
However,
as you can see from the second image above, that doesn't happen.
My question is: why?
Here is the document (which is completely self-contained):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Question</title>
<style type="text/css">
.mystery,
.mystery *
{
padding: 0;
border: 0;
margin: 0;
}
.button-container
{
background-color: #f77;
padding: 1rem;
margin: 1rem;
}
.button
{
float: left;
font-family: 'Courier', monospace;
width: 3.1rem;
height: 2rem;
border: solid black 2px;
margin-right: 0.7rem;
}
.button-wider-1
{
/*
The following 'calc' expression adds up:
* the standard width of a button (i.e. the width
of an element with class "button");
* the width of a button's border;
* the horizontal margin (i.e. the value of margin-right for a
button);
* the width of a button's border;
*/
width: calc(
3.1rem +
2px +
0.7rem +
2px
);
}
.button-wider-2
{
/*
The following 'calc' expression is the same as the previous,
except that there is an extra term at the end, the standard width
of a button.
*/
width: calc(
3.1rem +
2px +
0.7rem +
2px +
3.1rem
);
}
</style>
</head>
<body>
<div class="mystery">
<div class="button-container">
<button type="button" class="button">x</button>
<button type="button" class="button">x</button>
<div style="clear: both"></div>
<button type="button" class="button button-wider-1">y</button>
<div style="clear: both"></div>
</div>
<div class="button-container">
<button type="button" class="button">x</button>
<button type="button" class="button">x</button>
<div style="clear: both"></div>
<button type="button" class="button button-wider-2">y</button>
<div style="clear: both"></div>
</div>
</div>
</body>
</html>
I have observed this unexpected rendering in the following 3 browsers
(all on Linux):
Firefox ESR 52.5.2
Chromium 57.0.2987.98
Opera 50.0.2762.58 (the latest version of
Opera at time of writing)
You are calculating the width of your buttons by adding their width and their border-width. However, now browsers default to border-box for property box-sizing on buttons.
https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing
A quick fix is to add box-sizing: content-box; to .button so that reality matches what you expect about calculating widths.
Another fix would be to remove the two +2px from the second calc(); borders being part of the width of a button, you only want your second button to be "2 buttons + 1 margin". The first button is unchanged because you actually want it to be "1 button + 1 margin + 2 borders" so it'll work with both border-box and content-box.

CSS highlight label BEFORE an invalid input

Ciao, I have this element here:
<div class="uk-form-row">
<div class="md-input-wrapper md-input-filled md-input-focus">
<label>Label</label>
<input type="text" class="md-input">
<span class="md-input-bar"></span>
</div>
</div>
This is from a material design theme (Altair Admin v2) so the element once the page is loaded does this:
As you can see the label is moving around (but maybe is not a big deal).
With other elements, if they are empty (invalid) I can underline them or change their color using css:
input:invalid::-webkit-input-placeholder{
color: #e53935 !important;
}
But being this a label BEFORE the input I don't know how I can select it with CSS. How do I turn the LABEL into a different color if the input is invalid?
There is a simpler way to get this done. The :valid and :invalid pseudo-classes will automatically bubble up to a parent <fieldset>. Here is the reference.
You can take advantage of this fact to style your label like so:
<fieldset>
<label>Label</label>
<input type="text" />
</fieldset>
Then in your CSS
fieldset:invalid > label:first-of-type {
color: #e53935 !important;
}
So if your input is :invalid it will invalidate your fieldset, which you can then reference to style your label.
Look at CSS code (simplified to illustrate my point):
.md-input-wrapper {
position: relative;
}
.md-input-wrapper > label {
position: absolute;
top: 16px;
left: 4px;
right: 0;
}
Label is positioned absolutely relative to wrapper, so you can put label element after input element in HTML:
<div class="md-input-wrapper">
<input type="text" class="md-input">
<span class="md-input-bar"></span>
<label>Label</label>
</div>
After that, you can use General sibling combinator to select label of invalid input:
input:invalid ~ label {
color: red;
}

When keep typing without any space in a textarea, the display from the textarea showing in a div will exceed the width of the div?

Why keep typing without any space in a textarea, the output from the textarea shown in a div will exceed the width of the div? But the issue won't happen when typing with spaces.
function grab(){
var x= document.getElementById("text").value;
document.getElementById("replace").innerHTML = x;
}
<body>
<form>
<textarea id="text" onKeyUp="grab();" onKeyPress="grab();"></textarea><br>
</form>
<div id="replace" style="height: 20px; width: 100px;">1</div><br>
</body>
As there is no spacing added between your words, so it overflows text outside div no line-break is added, so to solve that you could add CSS word-wrap property to break a long word and wrap that to next line as soon as it reaches end of div.
word-wrap -
The word-wrap property allows long words to be able to be broken and
wrap onto the next line.
function grab() {
var x = document.getElementById("text").value;
document.getElementById("replace").innerHTML = x;
}
var ta = document.querySelector("form > textarea");
ta.addEventListener("keyup", grab);
#replace {
width: 200px;
height: 100px;
border: solid;
word-wrap: break-word;
}
<form>
<textarea id="text"></textarea>
<br>
</form>
<div id="replace">1</div>
<br>

CSS line spacing problem

why is the word BESTSELLER skipping a line... there is nothing in the code telling it to do so? (forgive me -- here is the link http://u.neighborrow.com/)
<div class="input text required">
<label for="ItemItem"></label><input type="text" id="ItemItem" value="" maxlength="255" style="font-size: 25px; width: 200px; margin: 5px 0pt;" name="data[Item][item]">
<input type="submit" value="Search" style="font-size: 22px" />
</div>
<?php echo $form->end(); ?>
Tips: <span style="font-size: 13px; position: relative; top: -2px"> Taking a trip? Want to try something before you buy it? <br > Have a project or event coming up? Want to see who has a copy of that bestseller?</span>
<br />
<?= $form->create('Item', array('action' => 'indextest')); ?>
</div>
I do not know what you mean by "skipping a line". Perhaps a link to the live code would help. But I did notice you have more closing div tags than opening.
EDIT: It appears the surrounding div has a computed line height of 30px. But the subject span has a line height of 18px. So, when the span takes up an entire line, it displays at the 18px line height. But when it only takes up a partial line, the 30px line height is applied to the remainder of that line, and this causes the last line to display at the larger 30px line height. Try setting the 18px line height at the parent div, or set the span to be display: block, so that the larger line height is not displayed in-line.
Try to add display:block to your span or replace the span width any block-level element such as <p> or <div>. In a semantic world you should use a <p> ;)
Also you should try to prevent inline-styles.

Resources