Nicholas Stimpson
Posted on December 31, 2019
Let’s start with a quick quiz. You know everything in CSS is a box, right? A box being one of those things with a content area, and padding, borders and margins around it. Of course you do. So in this simple HTML example below, starting from the outer div and including its descendants, how many block and inline boxes are used to render it?
Two? Well, there’s two elements, the inner div and the outer div. An HTML element becomes a box in CSS right? So just the two block boxes then. Sorry, no.
Four? All block boxes are block container boxes. Block container boxes contain as their children either (a) only block-level boxes or (b) only inline-level content. The inner div meets that requirement - it only contain inline-level content. But the outer div appears to be containing two inline-level text runs and one block box. When this happens, CSS automatically adds anonymous block boxes around the text runs. That way, the outer div contains only three block boxes and no inline-level content. And the two added anonymous block boxes contains only inline-level content, so now all the block boxes meet the requirement.
Seven? Block boxes cannot contain text runs directly though. Each one has a single child inline-level box called the "root inline box". So we have two block boxes for the divs, two anonymous block boxes, and three anonymous root inline boxes making a grand total of seven. Hurrah! we’re there.
The actual box tree arrangement looks like this…
DIV.outer block box (1) anonymous block box (2) anonymous root inline box (3) Text run: "The Vogons prepare to destroy the earth to make way for a hyperspace bypass." DIV.inner block box (4) anonymous root inline box (5) Text run "The dolphins depart." anonymous block box (6) anonymous root inline box (7) Text run "So long and thanks for all the fish."
So what’s the point of this? It’s just a reminder that the relationship between HTML elements and CSS boxes is rather more subtle than it may seem at first glance.
More Boxes than Elements
The display
property has a profound effect on both the type and number of boxes to which an element maps. Here’s some more examples of situations where elements generate either more than a single box.
The generation of anonymous block boxes described above applies for all block containers. So computed display values of inline-block, table-cell and list-item can all generate those boxes when necessary.
display:list-item
and the proposed but only currently implemented in Firefoxdisplay:inline list-item
also generate at least two boxes, a principal box and a marker box.
(The marker box is always a child of the principal box, even when it’s positioned "outside".)-
display:table
anddisplay:inline-table
elements also generate at least two boxes. A table-wrapper box and inside that, a table grid box. When CSS properties are assigned to an element withdisplay:table
, some of the properties are assigned to the table-wrapper box and some to the table box, but never both. For all the properties not assigned, the values used are the default ones for the property.
For example, any specified margin is applied to the table-wrapper box, so the margin of the table box is 0px all round. But any specified value of overflow is applied to the table box, so the overflow value for the table-wrapper box is always “visible”. As well as the table box, the table-wrapper box contains any boxes generated by the caption element if one exists. -
Table layouts can also result in anonymous table objects. The structure for HTML tables is
table
–tbody
–tr
–td
, which maps to the CSS display valuestable
-table-row-group
-table-row
-table-cell
. How each of these works within the layout depends on both their context and content.
So what happens if a div element is assigneddisplay:table-row
on its own in the CSS? It generates three boxes: an anonymoustable
orinline-table
box, which surrounds thetable-row
box of the element, which in turn contains an anonymoustable-cell
box, which contains the div’s contents. So if the HTML is:
<div style="display:table-row">Lorem ipsum dolor sit amet</div>
the box structure is
Anonymous table box (1) DIV table row box (2) anonymous table cell box (3) anonymous root inline box (4) Text run: "Lorem ipsum dolor sit amet"
Similarly, if a lone display:table-cell
element is present, it is wrapped in anonymous table-row and table boxes. Sufficient anonymous inside or outside boxes will always be generated to meet either the pattern table
- table-row
- table-cell
or table
- table-row-group
- table-row
- table-cell
.
So for another example, suppose we had:<div class="outer" style="display:table">
<span class="inner" style="display:table-row-group">
lorem ipsum dolor sit amet
</span>
</div>
that would form:
DIV.outer table wrapper box (1) DIV.outer table grid box (2) SPAN.inner table row group box (3) anonymous table row box (4) anonymous table cell box (5) anonymous root inline box (6) Text run: "Lorem ipsum dolor sit amet"
Fewer Boxes than Elements
On the other hand, there are a couple of display values that cause no box to be generated at all. display:none
and display:contents
do that. display:none
affects descendant elements as well so they do not generate boxes either, while the descendants of display:contents
elements generate boxes normally.
Why Boxes Matter
So can we make use of knowing this? It can help us understand why CSS doesn't always do what we expect it to, and it can help us spot solutions to particular layout problems.
For example, let’s suppose we have
we might have expected that the .cell
element would be 200px tall and the text "Hello World" vertically centered in it, since that's how table cell vertical alignment normally works. But it’s at the top. That's because the table-cell’s height isn’t 100% of the .outer
element, but 100% of the anonymous table box. And because it’s anonymous, it can’t be styled to be anything other than the default height:auto
, and auto means that it's the height of its content. Which is just the height of the text.
As a second example, suppose we have some nicely semantic HTML like this.
The default styling for displaying on a desktop might be just what we want. But on a mobile, we might want to compress it down a bit and for good responsiveness lay the title and list items using a flexbox layout. Now if we just give the div element display:flex
, it'll have two flex items, the first created by the h1 element and the second created by the ul element. Evening up the fonts and spacing, we get something like this:
That's not compressed it much. If we wanted the list items to follow on from the heading horizontally we could try making the ul element display:flex as well. However, doing that the items won't wrap under the heading, so we still get this:
Which hasn't helped. If we want the items to wrap under the heading, we need the list items themselves to participate in the same flexbox as the heading. Fortunately, if we realise that the flex items are the boxes created by the elements rather than the elements themselves, then we can simply remove the box created by the ul element, and the boxes created by the li elements will become the flex items. The div flex container will then have six items instead of two. All we have to do to remove the box created by the ul element id to apply display:contents
to it. And the result is this:
This article is a revised version. The original article described the boxes that would be generated according to the CSS 2.1 specification. The CSS Inline Module Level 3 specification makes some changes to that. Most notibly, if we have this markup <div>The <span>dolphins</span> depart</div>
(with default styling), in CSS 2.1 "The" and "depart" would each be contained in their own dedicated inline box. In CSS 3, there's a single root inline box which surrounds "the", the inline box generated by the span, and "depart". So in total, CSS 3 defines there to be one fewer box than CSS 2.1 does.
In addition, CSS 2.1 says that each rendered line contains a "strut" which is described as "as if" it were a zero width inline box. CSS 3 instead clarifies that, saying that the strut is a zero width glyph and only exists at all if there's no existing glyph that can do the job of applying a minimum height to the line.
CSS3 also makes clearer that when text breaks across lines, the inline box does not become two inline boxes, but is still one box, fragmented.
Comments, corrections and follow-up questions are most welcome. Even tl;dr.
Posted on December 31, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.