The Insane Perils of CSS vertical-align

The vertical-align property is really strange and generally just doesn't behave in a way that's intuitive to me at all. I don't think I'm alone in this so I decided to dedicate an entire post to the subject. Note though, this isn't actually a post about how to do vertical alignment with CSS in general. There are many ways to accomplish that objective, of which the vertical-align property is just one. It's often desirable to use vertical-align when the nature of the desired alignment has some relationship with other inline elements because vertical-align "knows" about the baseline of the text and is capable of moving things around relative to it (actually the same reason that it's such a pain).

Behavior for Tables vs Inline Elements

vertical-align has actually two seperate specifications for its behavior. One defines how it works when it's applied to inline elements (including inline-blocks) and the other defines how it works when it's applied to table cells. The behavior for table cells is actually pretty intuitive so I won't talk about that beyond the brief mention here. vertical-align on an element with display: table-cell (which includes actual table cells) works just like the valign property on a table cell. Basically, it just vertically centers the contents of the cell. The downside of this is that you're flirting with the general wonkiness of tables by setting that display even if you aren't actually using the elements within your html.

Top and Bottom

One of the really confusing things about vertical-align is that, while the values "top" and "bottom" work the same way, the value "middle" actually works in a completely different way. So, the gist of it is that "top" and "bottom" generally work the way you would expect - they align the top or the bottom of the current element to the top or the bottom of the line box it's in. The line box is just the smallest box that can contain a series of inline elements. So, basically, it just moves everything on a line to the top or the bottom of that line if there's room in the line box. Therefore, it's likely that the largest element in a line won't be affected by these alignments at all because it's already touching the top or bottom of the line box that it's in since the line box was made to wrap around the contents of the line. However, this might not be the case if some smaller elements are moved off of the baseline forcing the linebox to be bigger.

Middle

vertical-align: middle aligns the vertical center of the element with the baseline plus half the x-height of the parent where x-height is the size of a lower case letter in the current font. So, basically what this means is that we're centering inline elements on the baseline rather than putting them on top of it (which would normally happen). The big kicker is that this doesn't affect other elements on the same line. Therefore, unless we apply vertical-align: middle to other elements on the same line then elements with a small height will not move that much since they will move half of their own height down on the line (so if the element is full of text then it will only be moved down half a character).

However, a large element will be moved down half of its height as well and since the line box is made to contain the elements inside of it, this "moving down" effectively shifts the center of the line box making everything else appear to "come up" as well. This weird scenario yields the intuition that vertical align does nothing unless all of the elements in a line are vertically aligned. But, as we've seen, this isn't the case. It's simply that probably what you want is to make sure that, at a minimum, the tallest element in the line has been vertically aligned. If your elements are being generated dynamically and you don't know what their height will be in advance then you probably want to vertically align all of them when using "middle".

inline-block

inline-block is a really strange beast. When we put an image in a line of text the bottom of that image will be aligned with the baseline of the text so that the image seems to protrude upwards. An image is a rectangular thing so it seems intuitive that if we put another rectangular thing on a line of text then it will behave in the same fashion, e.g. an inline-block. But it doesn't at all. Instead, the block is flowed using the bottom line of text inside of it except when overflow: is set on the element to something other than "hidden". So, for most scenarios, setting inline-block on an element will actually make the element expand downards past the baseline when the height is adjusted which probably isn't what you want (I don't think it's ever been what I wanted). Fortunately, all we have to do is set overflow: hidden on the element and it will use the bottom margin edge as the baseline which is probably what you want. Alternatively, adding padding to the top of the element to expand it will do the correct thing because the padding will be added before the text that's being used for alignment. This comes up with vertical-align a lot simply because my intuition is often to use vertical-align to fix the problem that occurs here which doesn't work when the element is still being aligned by the line inside of it rather than by the bottom of the element itself. Of course, we don't have to use overflow: hidden to make inline-blocks work in a good way. We can instead use vertical-align: top or vertical-align: bottom because, as discussed previously, they align with respect to the element itself rather than its baseline.

line-height

Setting the line-height equal to the size of its parent element can be a really easy way to get text centered vertically but it has some weird caveats. The biggest thing to remember is that when you do this what happens when you use vertical-align: middle you are creating space around the baseline but the text conceptually doesn't move (it doesn't "push off of the baseline"). This means that if there is an element sitting to the left of our element that is large then the line box around both elements is already large (because it wraps both elements). If that element is protruding over the baseline (like an image) then we can't achieve vertical center with it by increasing the line-height because both will remain on the same baseline. If we make the line-height very large then we will get an approximate vertical center for both elements inside of their parent (because lots of space will be created around both) but our element still won't be vertically centered with the one next to it. What's worse is that if we set overflow: hidden on the parent then the extra space created on the bottom of the baseline (probably none on the top since element before our element is large and protruding up over the baseline) will just be eaten and so it will appear that nothing happened at all. This is, again, where vertical-align: top and vertical-align: bottom come in handy because they actually align to the top or bottom of the line box. This means that we can get our element to actually align to the top or bottom of the line rather than always lining up the text with the baseline of the previous element and so we can actually create a vertical displacement from the baseline.

Examples coming in the future (maybe...definitely upon request)