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.