cssdojo(re)learn CSS, the right way

The Flow layout (part 1) - the block and inline formatting contexts

Read this kata carefully. It may be the most important kata of the dojo as it addresses the most commonly misunderstood concepts of CSS and thus is one of the biggest sources of CSS frustration.

What is a layout?

So far, we’ve learned CSS properties to style one element and change its color, width, font size... but web pages are made of hundreds, often thousands of elements.

A layout is a set of rules and CSS properties that dictates how multiple elements will interact between each other.

Let me list some implications of this definition:

  • A layout rules how CSS properties will interact. It may use specific CSS properties (such as flex-direction for the flex layout), but it will also make use of standard properties (width, margin...)
  • Layout having different sets of rules, this means that a CSS value (such as width: auto;) can behave differently in the context of different layouts
  • It also means that some layout-specific properties (such as float) will be completely useless in the context of other layouts
  • Layout rulesets can introduce very specific behaviors (such as margin collapsing) that will not be transposed to other layouts
  • Each CSS layout has its own CSS specification

You should see layouts as a toolbox: within the context of a layout, you’ll be able to predict how elements will place next to each other. You don’t like a layout’s ruleset? Change your toolbox and use another layout.

There are four main layouts: flow (the default one), flex, grid, and table. There are others, but they are more anecdotic. As flow is the default layout and you won’t use flex on every single DOM element ever, you MUST master it. There is no way around it. Let me repeat that: No. Way. Around. It. This is the goal of this kata.

Other layouts will be addressed in the second part of the dojo. After learning all the main layouts, you’ll have a good understanding of which one is the more adapted to your specific case.

Again, a layout is a set of rules and CSS properties that dictates how multiple elements will interact between each other.

The formatting contexts

Hold on to your butts, because this kata is probably the most difficult part of this whole website but also the most important.

The flow layout introduces a concept: the formatting context. Think of it as a mini-layout inside the flow layout. The spec says that there are two formatting contexts in the flow layout:

The block formatting context
Block boxes are laid out vertically, one after the other, beginning at the top of a containing block.
The inline formatting context
Inline boxes are laid out horizontally, one after the other, beginning at the top of a containing block.

We’ll see how each formatting context works further down, but for now, keep in mind that there are block and inline contexts.

For this dojo, we will be working in English but take note that in other languages the writing mode of the page could change and invert the block and inline directions to horizontal and vertical respectively.

Let’s sum up what we’ve learned:

A drawing showing the four main layouts and the formatting contexts under the flow layout
All important layouts

How do I know which formatting context applies? This is one of the most confusing parts of CSS, because it is implicit. There are four rules:

  1. A formatting context always applies inside a block container box. We will see later how to create a new block container box but the <html> element creates the higher one in the box tree
  2. A block container box having only inline-level boxes as children will have an inline formatting context
  3. A block container box having only block-level boxes as children will have a block formatting context
  4. There can be no mixup between block-level boxes and inline-level boxes in a block container box

(Yes I know that using block container box and block-level box is super confusing but those are the exact terms in the spec.)

But what are block-level and inline-level boxes? Remember, in CSS everything is a box. Text-related elements such as <strong>, <em> or <span> will create inline-level boxes. Other elements such as <p>, <section> or <div> will create block-level boxes.

What if I have some text that is not wrapped in inline elements such as <span>? In order to fix the rule of the inline formatting context (it must contain only inline-level boxes), the browser creates anonymous inline-level boxes around unwrapped text. You can’t select them with CSS but you can still inspect them with the dev tools.

The dev tools inspecting the HTML generated in the editor above
An anonymous inline-level box as seen in the dev tools

What if I mix up inline and block elements, such as <span> and <block>? In order to fix the rule of the block formatting context (it must contain only block-level boxes), the browser creates anonymous block-level boxes around inline-level children. Again, you can’t select them with CSS and you can’t inspect them with the dev tools (but trust me, they are here).

One thing that must be cleared up before going forward is that all these layout rules are about boxes, not elements. DOM elements can create inline and block-level boxes, but then you can forget about the DOM and only think in term of boxes.

Furthermore, when creating boxes the browser will look at all the descendants in the DOM tree. By default, every element will be handled by its nearest parent block container box. We say that it will participate in the parent’s formatting context, or that it’s in the flow. That’s why the example below renders exactly the same as the example above, even if the DOM is different. All boxes are handled by the nearest block container box (created by the <html> element).

If we try to sum up all of this, the browser takes all the elements, turns them into boxes (block-level and inline-level boxes), arrange them in a block container box, introduces anonymous block-level and inline-level boxes and creates formatting contexts accordingly.

A drawing showing squares and lines at the top to represent block and inline boxes, and at the bottom the same squares and lines arranged in boxes and formatting contexts
How the browser transforms different elements into boxes and formatting contexts

Mastering the formatting contexts

What if I introduce a block element inside of an inline element?

Task: Put a block element inside the middle inline element and try to predict the result

Remember, we are dealing with boxes here. A formatting context doesn’t care about DOM elements. It only cares about block-level boxes and inline-level boxes. The browser will see five boxes to group together:

  • An inline-level box created by the first <span>
  • An anonymous inline-level box containing the "boxes " text
  • The block-level box created by the <div> element
  • An empty anonymous inline-level box resulting from the split of the middle <span>
  • An inline-level box created by the last <span>

Thus, the <div> breaks the inline formatting context and forces a block formatting context by introducing anonymous block-level boxes around the groups of inline-level boxes. The browser tries to reconcile other CSS properties in the most logical way possible. Here, you can see that there is a border on the bottom, left and top sides of the first anonymous inline-level box and the right border is on the last anonymous and empty inline-level box (increase the border’s width if it is not clear enough).

To change the default behavior of DOM elements, you must use the display property. This property has two possible syntaxes:display: [outer] [inner]; property is most commonly used with shorthands (block, inline, flex...) but here we’ll learn it with its full syntax to understand what happens.

In this kata we’ll learn this property with its full, two values syntax to better understand what happens. If you are on Chrome, you can temporarily switch to Firefox/Safari or use this matching table to achieve the same results:

Two-value syntaxShorthand
block flowblock
inline flowinline
block flow-rootflow-root
inline flow-rootinline-block

Now that we’ve cleared up these two syntax, let’s see how the display: [outer] [inner] property works.

The outer display type
This defines how the element should behave in the context of its parent. Possible values are block and inline.
The inner display type
This defines how the element’s children will lay out. Possible values are flow (participate in the parent’s formatting context), flow-root (create an independent formatting context using the flow layout), and other layout keywords such as flex and grid.

Thus, a <span> element defaults to display: inline flow; (same as display: inline;) and a <div> element defaults to display: block flow; (same as display: block;).

A table of display types combining outer display inline and block with inner display flow and flow-root
Different combinations of outer and inner display types

Now, let’s say we have a paragraph of elements forming an inline formatting context. We also have a block element, for instance a button, that we want to inline in our text.

Task: Inline the "button" in the text without changing its shape by using the display property

Have you found it?


...


If you already know the answer with the shorthand, try to do it with the outer/inner display syntax and truly understand why it works.


...


Congratulations! You just reinvented display: inline-block;!

Why display: inline; doesn’t work: by setting inline, you are explicitly setting the outer display type to inline (which is what we want because we want to inline the button in its parent) but also implicitly setting the inner display type to flow. If the inner display type is flow, the children of our button will participate in the parent flow. The only child of our button is an unwrapped text, therefore an anonymous inline-level box, therefore an inline formatting context will apply everywhere and our button will lose some of its block-level box properties that we want to keep. By choosing flow-root, we force the creation of an independent block formatting context.

Good! At this stage, you should have a good grasp of when and where formatting contexts apply, and how to switch between them. Don’t hesitate to let your brain cool off and think about it twice. I’m repeating myself but it probably is the most confusing concept about CSS, and it is really important to truly understand it.

The block formatting context

Now that you know what are formatting contexts and where they apply, we can learn their rules. Remember, a layout is a set of rules and CSS properties that dictates how multiple elements will interact between each other, and a formatting context is a mini-layout inside the flow layout. Let’s learn the rules of the block formatting context.

  • Boxes are laid out one after the other vertically
  • The width and height properties are honored, by default the box will consume all the space in the inline direction (full width of the container) and be as tall as its content
  • The margin property sets the vertical distance between two sibling boxes
  • Top and bottom margins (not horizontal ones!) between two adjacent block-level boxes that have no content between them (border, inline element...) collapse: they combine in a margin whose size is the largest of the individual margins

Task: Here, every margin collapses and every block is 10px vertically from its adjacent boxes. Try to break the margin collapsing in at least 3 different ways

The inline formatting context

Next, we learn the rules for the inline formatting context. Let’s bring up the schema from the styling text kata to understand what is the line height and the baseline:

A lorem ipsum text with arrows pointing to different characteristics of a line of text
Schema of a line of text and related CSS properties
  • Boxes are laid out on the baseline one after the other horizontally in the writing direction
  • If there is not enough space, the boxes break into a new line
  • You can’t set width or height on inline boxes
  • The height of a line is defined by the tallest box in it
  • Margins work only in the inline direction

Task: Make the "first" line bigger by changing its font-size and the "second" by changing its line-height

  • The vertical-align property sets how an inline-level box should behave in the vertical direction. Possible values are baseline (the default), top (the box will stick to the top of the line), bottom (same but at the bottom of the line), middle (centers vertically inside the line, which is not the same as baseline!), sub and super for exponents. Go ahead and try all those values:
  • The word-break property controls how words are broken when the text wraps. Classic values are normal and break-all (can break after any character)

Task: Break the super duper long word!

  • The white-space property controls how the browser handles white spaces in the generated HTML. By default, white spaces are combined and line breaks are ignored (so that you can indent HTML as you want without interfering with the content). You can change that by setting pre-wrap to preserve white spaces and line breaks or nowrap to prevent normal text wrapping.

Task: Make the empty line in the HTML paragraph appear

What I should remember

Ooof! That was a hard one. Now you should have enough knowledge to truly understand and use the flow layout! In the next katas, we’ll focus on edge case behaviors that break the flow layout such as overflowing content and positioned elements.

  • Layouts are rulesets that enable developers to place elements relative to each other
  • There are four main layouts: flow (the default one), flex, grid, and table
  • The flow layout has two mini-layouts: the block formatting context and the inline formatting context
  • The formatting contexts are applied at the block container box level. The <html> element creates a block container box, display: flow-root; is another way to create one
  • Inside a block container box, the browser turns all DOM elements into block-level and element-level boxes (including all descendants, by default they participate in the parent formatting context)
  • A block (or inline) formatting context will be used if all children are block (or inline)-level boxes
  • The browser fixes the box tree with anonymous block-level and inline-level boxes
  • The display: [outer] [inner]; property changes the outer display type and inner display type of an element
  • The outer display type tells the browser if the element should behave as a block-level or inline-level box in the context of its parent block container box
  • The inner display type tells the browser what layout should apply inside the element. For instance, flow, flow-root, flex, and grid are all valid values
  • You’ll always see and use those shorthands:
    • display: block; is the same as display: block flow;
    • display: inline; is the same as display: inline flow;
    • display: inline-block; is the same as display: inline flow-root;
    • display: flex; is the same as display: block flex;
    • display: inline-flex; is the same as display: inline flex;
    • (same thing with grid and table)
  • The rules of the block formatting context are:
    • Boxes lay out vertically and consume all the space in the inline direction
    • Margins collapse between adjacent boxes that don’t have content between them
  • The rules of the inline formatting context are:
    • Boxes are laid out horizontally on the baseline
    • There are restrictions on width, height and margin
    • You can control the flow of the text with vertical-align, word-break and white-space