Revisiting CSS Vertical Alignment
Sun, Mar 20, 2011For the most part, the CSS vs Tables battle has been played out and a practical kind of stale-mate has arisen where tables are used for what they were designed for (tabular data), while CSS is used for everything else…
… except for when it simply doesn’t work! Browser bugs, spec ambiguities and developer misunderstandings lead to all sorts of problems with form layouts, complex page layouts and practically any kind of vertical alignment issue.
Which is when those of us with a practical bent simply give up and use tables!
I haven’t visited the CSS vs TABLES debate in this blog yet, and I really don’t intend to discuss it in any serious depth. I will simply say that while I highly value separation of content from presentation, and like using CSS for styling (although I wish it had variables), I am not a fan of using CSS for layout. Neither am I a fan of using TABLES for layout, both approaches have serious drawbacks, but unfortunately they are all we have until the CSS3 layout related modules are more widely available (or until we all go back to native app development!)
However, as the browsers continue to improve, and IE6 finally shuffles off its mortal coil, some of those instances where we might, in the past, resort to tables can be revisited with fresh eyes and see if we can finally let go of some of our legacy ways.
One of those areas where we struggle in CSS is with vertical alignment…
The CSS vertical-align attribute
Example 1: A TABLE
In pre ‘semantic markup’ days, if you had a number of inline elements that needed vertical alignment,
you used a table, with the vertical-align: middle
style for its cells:
<style>
* { border: 1px solid #1080F0; }
.small { font-size: 0.5em; }
.large { font-size: 2.0em; }
#ex1 td { vertical-align: middle; }
</style>
<table id='ex1'>
<tr>
<td><input type='checkbox'></td>
<td><input type='text'></td>
<td><span class='small'>small</span></td>
<td><img src='avatar.gif'></td>
<td>normal</td>
<td><span class='large'>large</span></td>
</tr>
</table>
Example 2: vertical-align on a DIV
To do this in pure CSS without any tables, a first instinct might be to use vertical-align:middle
on
a <div>
, but as you can see, applying this attribute to a block element has no effect on its children:
<style>
#ex2 { vertical-align: middle; }
</style>
<div id='ex2'>
<input type='checkbox'>
<input type='text'>
<span class='small'>small</span>
<img src='avatar.gif'>
normal
<span class='large'>large</span>
</div>
Why? If you read the spec you will see that it only applies to “inline-level and table-cell elements” - This means that the only block level element that it can affect is a table-cell, which we saw in example1.
Example 3: vertical-align on descendants of a DIV
Instead you need to find a way to apply the attribute to the inline children. You can apply vertical-align to each child individually, but its easier with a descendants (*) or child (>) selector.
<style>
#ex3 * { vertical-align: middle; }
</style>
<div id='ex3'>
<input type='checkbox'>
<input type='text'>
<span class='small'>small</span>
<img src='avatar.gif'>
normal
<span class='large'>large</span>
</div>
Again, reading the spec you will see that:
- “This property affects the vertical positioning inside a line box of the boxes generated by an inline element”
Errr, ok. Thats a bit tricky to understand. The ’line box’ generated by the inline elements is simply a box big enough
to contain all of the inline elements. In this case, since our <div>
naturally wraps all of the inline elements, that
line box is exactly the same as our containing <div>
and we get the vertical alignment behaviour that we want.
Example 4: a DIV with height
So far so good, this will work in all modern browsers (Chrome, Safari, FF2+, IE8+), assuming that the browser is in standards mode and not quirks mode (e.g. using a DTD specifying either HTML4.01 or HTML5)
However, it assumes that the <div>
itself is flowing naturally around its contained inline
elements and is therefore the same size as the generated line box around the inline elements.
But what happens if the <div>
itself has a height:
<style>
#ex4 { height: 5em; }
#ex4 * { vertical-align: middle; }
</style>
<div id='ex4'>
<input type='checkbox'>
<input type='text'>
<span class='small'>small</span>
<img src='avatar.gif'>
normal
<span class='large'>large</span>
</div>
Here we can see that the vertical-align attribute no longer does what we expect.
The reason ? Well, the ’line box’ we have been referring to did not change simply because we gave the
containing <div>
a height. In the image below we show the outline of this imaginary line box. As you can
see the elements are vertically aligned within their line box, but this doesn’t really help us.
Before we examine how to fix this, lets look at another example where vertical-align on child elements does not work as expected:
Example 5: a DIV with child floats
If we go back to example3 where the containing block naturally wraps its child elements, but we introduce some additional floated children, we can see that the floated children do not respect the vertical-align attribute:
<style>
.lfloat { float: left; border-color: red; }
.rfloat { float: right; border-color: red; }
#ex5 * { vertical-align: middle; }
</style>
<div id='ex5'>
<div class='lfloat'>lfloat</div>
<div class='rfloat'>rfloat</div>
<input type='checkbox'>
<input type='text'>
<span class='small'>small</span>
<img src='avatar.gif'>
normal
<span class='large'>large</span>
</div>
There are actually 2 reasons this doesn’t work. Firstly, the floated elements are block elements and we have already established that vertical-align has no affect on block elements, but even if we switched them to inline elements this example would still break because floated elements do not respect vertical-align either.
NOTE: I have not been able to track down which part of the CSS spec covers vertical-align on floats so if anyone knows exactly why floats don’t respect vertical-align please let me know!
THE SOLUTION: a DIV with line-height
These 2 problems can be solved with the same CSS answer, and that answer is to apply a line-height to the containing block element:
<style>
#ex6 { height: 5em; line-height: 5em; }
#ex6 * { vertical-align: middle; }
</style>
<div id='ex6'>
<div class='lfloat'>lfloat</div>
<div class='rfloat'>rfloat</div>
<input type='checkbox'>
<input type='text'>
<span class='small'>small</span>
<img src='avatar.gif'>
normal
<span class='large'>large</span>
</div>
The explanation for this solution can again be found in the spec:
- “On a block container element whose content is composed of inline-level elements, ’line-height’ specifies the minimal height of line boxes within the element”
What this means, in English, is that the line-height on the <div>
sets a minimal height on the elements within it, thus forcing the invisible
generated ’line box’ to be the same as the height of the <div>
and that gives us the vertical alignment behavior needed.
To Summarize…
… don’t use vertical-align on block elements (except table cells). Instead, use it on the child inline elements and they will become aligned within their generated line box.
Additionally, you might need a line-height on the container in order to get the generated line box to match the container’s height.
Helpful Articles
- All you need to know about Vertical-Align
- Vertical-Align Misuse
- Applied CSS Vertical-Align
- How (Not) to Vertically Center Content
- Flexible height vertical centering with CSS
- The CSS3 Flexible Box Layout
Yup. Sometimes you gotta just read the spec:
One day, one happy day, we will finally be able to use some of these:
Where my tendencies lie in the css vs tables debates: