A presentation at Web Directions hover in April 2021 in by Rachel Andrew
It is 23 years since CSS1 became a recommendation in December of 1996. In August that same year Internet Explorer 3 shipped, the first browser to have CSS support.
The CSS1 specification is tiny, however it contains the fundamentals of the language we use today. As such, a website built in 1996 using CSS1 will still work today, in a powerful modern web browser. The CSS1 spec contains a set of amazing ascii art diagrams, detailing the CSS formatting model, which would in CSS2 become known as the Box Model.
I have no idea how many times I have drawn a diagram of the Box Model, explained the Box Model I’ve written about hacks to get round the fact that at one point Internet Explorer incorrectly implemented width as inclusive of padding and borders when other browsers implemented the spec correctly using the content width as the width. The CSS1 specification also includes the rudimentary explanation of what is likely to be one of the very first things you learned about laying things out with CSS. If you have done even the smallest amount of CSS there are two concepts you are likely to have encountered.
An underrated, yet brilliant fact about the web is that if you create an HTML document, mark it up properly, you get something readable. We don’t have to position every line of text. Mark your content up with HTML, pop it on the web and your document is published. This works because browsers have an inbuilt stylesheet dictating some basic styles used by HTML elements. In addition to things like making headings larger and bold, and giving lists bullets, it causes some things to become block elements.
Block‐level boxes include by default things like headings and paragraphs. They have certain key features. They stretch out in the inline direction to take up all the available space. You can give them a size in both dimensions and this will push other things away from them ‐ as will any margin or padding applied to them. They always break onto a new line.
Inline‐level boxes behave like words in a sentence. They do not break onto a new line. You can give them margins and padding but in the block direction this will not cause other elements to be pushed away from them.
I’ve added padding to the span, you can see it has pushed things away in the inline direction, making more space between the words either side. In the block direction however the background color is going over the line above and under the line below – I’ve used a semi‐transparent color so you can see.
We can choose the correct semantic HTML element for the job, then use CSS to change how it looks. We can change between block and inline. This is an important feature of the web as it means we can pick the right element for the job and not because of how it looks. 11
This is pretty much how we have taught CSS since CSS became a thing. Inline things, block things, the box model. However in the past couple of years I’ve completely changed how I teach CSS layout, because these concepts only give part of the picture, our leaning on them as the fundamentals of CSS leads to confusion as soon as the new layout methods of grid and flexbox are introduced. This talk is the result of a couple of years of experimentation in terms of teaching layout, and it all starts with display.
The CSS display specification is the key to unlocking CSS layout. You have to understand display, as it pus the rest of layout into context.
It’s a Level 3 specification, as much of the content is based on CSS2.1. CSS2 took the simple ideas contained in CSS1 and refined them, a process that continued with CSS2.1 ‐ and did you know we even have a CSS Level 2.2? A lot of specs which are at level 3 and beyond still refer to CSS2 for key concepts. So you can see modern specs as building on this work, it’s only when greater clarification is needed that a spec is created that essentially replaces what is in CSS2.
Let’s go back to block and inline for a moment. What does block and inline actually mean?
This block‐level paragraph with a background color is expanding to fill all of the available width. If we introduce a second block level thing it goes below, on a new line.
The paragraphs are displaying one after the other in the block dimension. They expand to take up all of the space in the inline dimension.
The block dimension is the direction that paragraphs lay out in your writing mode.
The inline direction is the direction in which sentences run in your writing mode. So the block dimension is the direction that paragraphs lay out in the writing mode of the document. The inline dimension is the direction in which sentences run in the writing mode of the document
Change the writing mode to vertical‐rl and the block dimension now runs right to left, and the inline dimension top to bottom. Block and inline is tied to the writing mode of our document.
When we describe something as being block‐level it is a box that will take up all space in the inline direction and appear after the previous block‐level box in the block direction of our layout.
And these block‐level and inline‐level things make up the Normal Flow of the document, so normal flow is sometimes referred to as block and inline layout.
A well-structured HTML document means you are working with the browser rather than against it.
This normal flow is what your layout will always try to return to. This means that working with it makes your life much easier. With a well‐structured document you are able to make changes to normal flow where it makes sense and take advantage of the fact that CSS behaves like this, giving you a readable view of the content, and save yourself some work. While we’re here. In Normal Flow. My husband likes to joke with me that I travel the world talking about little boxes. The thing is, if you talk about CSS it’s all little boxes, its little boxes all the way down.
It’s pretty easy to think about block‐level things as a box. We’ve got an element ‐ a paragraph or a div and it creates a block‐level box. We can see a boxy thing if we give it a background color or a border. CLICK We can see it in DevTools and it maps to our understanding of the box model.
However everything is creating boxes.
Inline‐level things create inline boxes.
Lines of text such as the lines inside our paragraph create a special kind of box called a line box. It’s all boxes. All of our problems and confusion come from misunderstanding the way these boxes behave, and how they react to each other. We need to understand these boxes, and the display spec holds the key.
https://www.w3.org/TR/css-display-3/#intro The CSS Display Level 3 specification details how these boxes are generated, building on the work of CSS2. The spec says, “for each element, CSS generates zero or more boxes as specified by that element’s display property.” We’re right into the action here with the boxes created by elements. The browser looks at the value of display for all of our HTML elements and creates a box, or several boxes.
Something with display: block gets a block‐level box.
Something with display: inline gets an inline level box.
Creates a block-level box with flex children.
If you have made an element a flex container, then it gets a block‐level box on that element, and any direct child elements get boxes which are flex items, and so start behaving in the ways we expect flex items to behave. The flec container creates a flex formatting context for the children.
New CSS terminology! What’s a formatting context? It’s really just a fancy way of describing behavior. CSS Specs use all these odd words, because we have to try and be as succinct as possible. Therefore we have these word and phrases that spec editors and browser engineers understand. In the specs these are usually ‐ assuming the spec editor remembers ‐ linked to a definition. So if you come across one of these odd phrases you can figure out what we mean. The good thing about standard phrases is that even if they are a bit weird, once you know what they mean they always mean the same thing which helps with clarity.
Describes the behavior of the child elements of a box. So a formatting context describes how things behave. In Normal Flow items participate in a block formatting context, or an inline formatting context. The paragraphs on your page are participating in a block formatting content display one after the other in the block dimension, the words and other inline elements in your paragraph are participating in an inline formatting context ‐ that is laying out one after the other in the inline direction. If you apply display: flex the items inside that container start to participate in a flex formatting context, so you now know that the normal flow rules no longer apply, instead you need to look at the flexbox spec to find the detail of our your items in that formatting context behave. 29
So we now have a block‐level box with children participating in a flex formatting context. We know it’s block‐level because because it has expanded to take up space in the inline direction.
There is available space. However if we add some following text it won’t hop up into that space.
It will display after the block level item. We could use that available space with justify‐ content: space‐between.
To distribute it between the items.
If we used a value of inline‐flex, in this case the flex container becomes an inline‐ level box, with flex item children. If we tried to use justify‐content: space‐between here nothing would happen, because the inline level box wraps the flex children.
Creates a block-level box with children participating in a grid formatting context. The same for grid, any elements with display: grid become a block‐level box, and their direct children become grid items. By default you get a one column grid, but as soon as you add other properties to the grid container you start to see grid‐like behavior. Once again, if the grid hasn’t been given a size, it will expand in the inline direction – so width for us working in a horizontal‐tb writing mode. The fr unit tracks can then distribute that space between them.
display: grid; grid‐template‐columns: 1fr 2fr 2fr;
So you can see that when the value of display is identified, it does two things. It defines how the box you have applied it to behaves in the layout. Is it block‐level or inline‐level? It also defines how the direct children of that box behave. 36
Refactoring the display specification.
It can’t be denied that the introduction of all these layout methods has made display seem a little more complex and, to some extent has powered a refactoring of the display spec, in order to better reflect what is going on when you change the value of display.
In the refactored specification however we can be more clear with our intentions by using the two values of display. These have been implemented in Firefox, and our display: flex becomes display: block flex we are explicitly asking for a block‐level box with flex children. display: inline‐flex asks for an inline level box with flex children.
display: inline flex
display: block grid
Regular block layout also has two values. A block‐level box with normal flow children? display: block flow.
Relatively recently CSS gained a new value of display, flow‐root. This solves a very common problem.
You have a floated item inside another element. You want the floated items to be visibly contained by the parent. However it is poking out the bottom of the box. When you float an item next to some text for example, then what happens is that the line boxes around the text become shortened to make way for the float, the actual element itself is not. So we’re right back to those boxes again already!
One way to fix this would to be by using a clearfix hack, another common thing is to use a value of overflow to clear the floats. That’s just a weird trick people know about ‐ changing the value of overflow to something other than the default visible clears floats. It does this via a very specific mechanism, that of create a new block formatting context. We already met formatting contexts, our page is a block formatting context therefore creating a new normal flow. Our flex container creates a flex formatting context for its children. Setting overflow to something which might cause scrollbars creates a new block formatting context because if you have a scrolling box in your layout, you don’t want things poking out of it, I don’t even know what that would look like. So you end up with essentially a contained mini layout inside your layout ‐ nothing can escape out, and nothing can poke in.
Changing the value of overflow works in most cases but is a bit obscure, so recognising that sometimes we want to create a new block formatting context we have this value flow‐root. Setting the value of display to flow‐root creates a new block formatting context. In a two value world that will be display: block flow‐root as you are creating a block level box which is a new flow‐root.
An inline box that creates a new formatting context.
So we then have a matched value pair. Just as we have inline grid and inline flex, we have inline flow‐root. We have display: inline flow‐root. And we already have a value of display which creates an inline flow‐root. display: inline‐block. We’ve been using display: inline‐block since the days of CSS2. An inline element displays like a word in a sentence, within the line box. If you give it padding, or a size in the block dimension this won’t push away any content either side. To make that happen, but without turning the item into a block‐level element thus causing it to break onto a new line we use display: inline‐block. 47
However display: inline‐block also contains floats. In this example I have a floated item, which is inside the inline element. As you can see it is breaking out of the container.
Set the container to display: inline‐block and the float is contained. So display: inline‐block is really creating an inline‐flow root, and that’s what the value is in the two value world, display: inline flow‐root.
And talking about it like this means that inline‐block stops being this weird trick we play, for years I didn’t really get what was happening here. Now it is properly described it’s much easier to understand.
Do not collapse through a new formatting context. Something worth stressing is the point about new formatting contexts also preventing margins collapsing.
These items are three divs inside a wrapper div they all have margins of 1em on all sides, the margins between te items collapse, but the reason the first and last items have no top and bottom margin is that it has collapsed through the box. 52
You can see it in devtools.
Creating a new formatting context with flow‐root
Or flexbox or grid, contains the floats. As everything inside a new formatting context stays inside.
Back to our boxes. We know that any element in our document gets a box. And these create a Box Tree, as our boxes nest inside other boxes. Each parent has a set of child boxes, which themselves contain more boxes.
However there are some things which don’t have an element to turn into a box. For example. If I have an element, which contains a string of text, part of which is wrapped in a span, then I turn the parent element into a flex container with display: flex. The span element gets a box, which is a flex item. However I get two more flex items as the two runs of text either side get wrapped in an anonymous box. You can see how these items no longer participate in an inline formatting cotext as the space between them is gone.
We can do all the standard flexbox things with these three items. For example we could use justify‐content: space‐between to space out the items.
Anonymous boxes are created to contain free ranging bits of text. In the flex example they become flex items, but even in normal flow, anonymous boxes will be wrapped around any string of text not inside an element. In normal flow you don’t ever need to think about it, it’s only once we are in flexbox or grid and these anonymous boxes start acting like flex or grid items, that it matters.
So in this example we have the box of the span on item two. CLICK It’s an actual element, this means we can style this element using a direct child selector. CLICK I’m saying style every direct child of p in the green color. CLICK however that doesn’t get the anonymous boxes, because while they exist in order to allow the flex layout to work, they are not a real element and so not a direct child of p.
Another place you find these anonymous boxes is when you use display: table‐cell.
Tables have structure, a cell is inside a row, which is inside a table. Therefore if you make two elements table cells, by setting them to display: table‐cell, an anonymous wrapper will be added to fix up the need for a tr, if you don’t have anything set to display: table another to fix up the need for the table.
Changing the formatting context away from block and inline layout, means some things no longer do what we are used to.
You now know that everything on the page is participating in some kind of formatting context. We have block and inline layout, normal flow, then we have some elements which ‐ by way of the value of display ‐ become flex or grid containers thus causing their children to take on a different formatting context. This means that some of the things we are used to in CSS don’t work, or don’t work in the same way due to no longer participating in a block formatting context.
Taking items out of flow.
If we’re in Normal Flow then we have some other properties that we can use to do layout, the time‐honored float, and position. Both of these methods are described as taking an item out of flow.
Setting an item to position: absolute is probably the first thing we think about when we think about things being out of flow. If we have content in normal flow like this.
Then use position: absolute the item is removed from its spot in normal flow, the space it would have taken up disappears, and the element can then be positioned according to its containing block. Managing overflow is now your job, as you’ve just hoiked the thing out of normal flow, where the browser would deal with that.
As we saw when looking at flow‐root however, floating an element also takes it out of normal flow. The line boxes of following items honor the element, but the actual box of the following element ends up behind the floated thing. The same as we see when we make an item position: absolute and the space it would have been taking up is removed.
The floating and positioning behavior we understand is specified for normal flow, for block and inline layout. They behave differently, or don’t work at all in other formatting contexts.
The thing to remember here is that this behavior is specified only for items in Normal Flow. It does not apply in the same way when the item that is floated or positioned is participating in a flex or grid formatting context. However the respective specifications do explain to us what will happen. The result can be marvelous or frustrating ‐ depending on what you are trying to achieve.
If I have a floated item, and then make the parent a grid layout. The float is now removed. So the item becomes a grid item and will start to behave in the way that gridex items do. This is because the item is no longer participating in Normal Flow, so regular float rules do not apply. This can be really useful, as it means you can use floating to create fallback patterns which you will then override with grid or flexbox.
This set of cards are floated.
Where it is less useful is where you would like to have floating behavior but also for floated things to utilize the lines of the grid. That’s not currently possible, and so a common pattern in editorial design is not available to us. As I mentioned in last year’s talk I think this is actually a pattern better solved with the Exclusions spec than with grid, and I’ve linked to the articles discussing it in the notes.
You can absolutely position items in a grid layout.
Absolute positioning does work in grid layout and flexbox however you need to be aware of how positioning works in these layout methods.
In this example my grid container has position relative and I have placed an item on the grid using the grid lines. The item spans 4 column tracks and two row tracks. It works so far as normal, he fact the container has position relative doesn’t change anything about the grid layout.
If I give the item a width and height then absolutely position it. The grid area it is placed in acts as the containing block. Not the grid container. So the offsets are from the boundary of the area.
Anonymous boxes created to fix up the box tree do not get generated once the item participates in a grid formatting context. What about other values of display, what happens if you use display: inline‐block or even display: table‐cell? Once again, if their parent becomes a flex or grid container then the child with display: inline‐block or display: table‐cell is now participating in a grid or flex formatting context and so these values no longer apply. An interesting detail is that of the anonymous boxes we learned about earlier. If an item is display: table‐cell it will have anonymous boxes to fix up the box tree. The spec details that these will not apply.
Precise details ensure that each browser does the same thing, makes for happier web developers!
And I think this is an important point to make, all of these interactions are detailed in the specs. If they weren’t browsers would have to figure out what to do, and might do something different.
The Web Platform Tests project has tests against web platform specifications, so user agents can check they are conforming. And this is all testable, one thing we need to ensure as spec editors is that our spec has tests. So for any bit of spec text there should be a test. So if you want to build a browser, and you are writing the browser code that will deal with how grid interacts with absolute positioning, you can find the test and make sure that your browser does the right thing. This is how we get interoperability, a web where each browser does the same thing, we write clear specs and we make them testable.
Everything generates a box – unless you ask for no box. In the display spec is the definition of two values which don’t generate boxes.
Display: none has been around since the beginning. If you apply display: none to a box it is as if it was never there. The box and all of its children are removed from the box tree.
Importantly, this happens for visual rendering – on a screen or in print AND for speech output. If something is display: none then screenreaders will not announce it or interact with it either. Display: none is the only value which behaves like this, the other values of display only affect visual layout. You should be able to change the value of display without changing the semantic meaning or interactivity of an element. It’s worth keeping this fact in mind as we discuss display: contents.
Where display: none removed the box and all the children from the tree. Display: contents removed the box but leaves the children. In terms of layout it is as if they were promoted to being direct children of their grandparent.
So If I have a layout like this, with an item, then a ul with list items. I want the list items to participate in the flex layout, however they can’t because the ul is a direct child of the element with display: flex, but the children are not.
Using display contents removes the box of the ul, the children now are promoted to the flex formatting context created by the div.
As I mentioned, it is only display: none that should affect whether an element is read out by a screen reader, and whether its children can be interacted with. Due to a bug in all of the browsers that implemented display: contents however, in the initial implementations using display: contents would alwso remove the semantic meaning of an element. So in our example the list would no longer be announced as a list. Firefox have fixed this, Chrome 89 also fixed this so it will make its way into other Chromium browsers including Edge. Safari have yet to fix this. So please take some care when using display: contents for the time being.
We have some interesting box generation things that happen. For example, think about lists. If we have a ul that essentially becomes a block‐level box. Its children get the special box treatment, as they become display: list‐item. I don’t know about you, but I kind of always thought that the special stuff that happened to lists was something to do with the parent, and it’s not. All the listyness happens on the children. It’s much like flexbox or grid, we make the parent a flex or grid container which causes their children to do flex or grid things, same for lists. It’s the children you want to watch out for.
With our list items it’s new CSS terminology time! The content of your list‐item becomes the Principal Box. And another box is generated, which becomes the marker box.
With list items, the principal box does not include the marker, that’s a separate box. The principal box goes around the content. The marker goes round the bullet
You can see this if you put a background color on the list item ‐ it doesn’t go behind the marker, just behind this principal box. List markers are actually really interesting and deserve a little sidenote of their own. For a very long time it has been impossible to style the things independently of the content. If you wanted to change the text on the item to white but leave the bullet black, you can’t.
li:first‐child { color: white; } Changing the color changes the bullet and text. To get what you want you have to wrap a span around the text, and style that. 92
In the CSS Lists Specification the ::marker pseudo‐element is introduced. This allows for styling of the list marker. We can replace our need for a span and this slightly weird approach by styling up the marker like so.
You can see the marker when inspected with DevTools, Firefox is currently the only browser to implement marker, however it has been implemented in Chrome behind a flag so it is on the way there too.
You can use generated content on the marker, adding a string of text perhaps.
And, just as we can pick an element which is normally inline and make it block, it is perfectly reasonable to take an element which is not a list item and make it into one. For example, we could take a heading, use display: list‐item this would give us a bullet for our heading, which we can then do something with ‐ for example add an emoji.
There are so many answers to CSS questions hidden in understanding these boxes. That when the browser comes to render the page it needs to decide the display type of the element, and therefore the display type of the children. Then it moves onto the children, what display type are they and what are the children. If there is no other answer than the default layout method is normal flow, block and inline layout. Grid layout and flexbox aren’t anything different, they are a value of display, and we should use the value of display that makes the most sense for the design pattern we are trying to create. The fact that we have a Grid Specification and a Flexbox specification doesn’t change that ‐ we also have a lists spec and a tables spec. Essentially what these independent specs are is working group process. It stops us ending up with one giant spec, once you have changed the value of display to grid, and have grid items, the grid spec explains how they work. Once inside an item which is participating in grid or flex layout we go right back to normal flow unless you have changed the value of the child too.
They act on the principal box and its direct children; the grandchildren go back to normal flow. Because values of display do not inherit, they will only act on the principal box and the direct children. We saw this with display contents. We can remove a box to cause the grandchildren of an element to be promoted to the items participating in a grid or flex formatting context. We can’t however cause the value of display to inherit, all we can do is start over with a new formatting context, be that grid, flex, block or inline.
So what is going on with subgrid, is this not some kind of layout inheritance there? Well, no. Let’s take a look.
Allowing track definitions to be inherited by a grid on a child. What subgrid allows is for the track definitions on a parent grid – the number of tracks, sizing, gutters and names, to be used on a child grid.
If I have a grid layout, a 6 column grid, with a pattern of columns. the row of boxes are each sat in an individual column.
And with the grid inspector turned on you can see what the grid looks like
I then have a component, which has an internal three column grid.
If I place this component onto my main grid, the internal grid on the component does not line up with the parent grid. This is because while the component is creating a grid formatting context, it is completely independent of the parent grid. You can see that when the component is here, because I have used the 2fr 1fr 2fr pattern, it almost lines up.
But if I move it so it spans 2 small and one big column it now doesn’t line up at all.
If I use grid‐template‐columns: subgrid however, the tracks from the parent become the tracks on the child. We’re not inheriting values of display here. If we remove display: grid from the component it stops being a grid so subgrid no longer applies.
7RXVHVXEJULG First create a grid formatting context with display: grid. Then opt in columns or rows with the subgrid value. We have to first create a grid formatting context and then, assuming the grid container of my component is a grid item itself we can use the tracks of the parent. 109
And we can now move the component around the grid and it stays lined up. Note that everything else about the grid remains independent. For example we now have gaps between out items because the parent has gaps. But we can change that on the subgrid by setting gap to 0, just as if the grid was independent.
And we now have no gaps, essentially it acts as if there is a negative margin on each side giving extra space to close the gap.
The subgrid must be participating in grid layout and a grid container itself. So to use subgrid your item must already be part of a grid layout, it must be participating in a grid formatting context, then it also needs to become a grid container. The only inheritance is that your tracklisting is taken from the grid tracks the child grid spans, if you have set the value of grid‐template‐rows, grid‐template‐columns or both
So that’s display! I hope after this you have more understanding of this key CSS property and all of the other things that are enabled by it. Do take a look at my resources and code to find out more.