Looking at normal flow, flex, and grid

CSS Layouts - Do we even need flex anymore?

CSS Layouts - Do we even need flex anymore?

Looking at normal flow, flex, and grid

CSS Layouts - Do we even need flex anymore?


CSS supports a number of different layout modes, and understanding which one to use in a given situation is a fundamental skill needed to use CSS effectively. It determines how child elements are positioned relative to their parent, and affects how layout-related properties like width and margin work.

Today, we're going to cover the most commonly used layouts - normal flow, flex, and grid. We'll also take a look at multi-column layout and float, as these mostly act like variants of normal flow.

Text-based layouts

Normal flow, multi-column and float are all layout modes which prioritise laying out text easily. They are seen most commonly in apps that involve users reading content, rather than interacting with it - like news articles, wiki pages, recipes, and social media posts. However, any app which includes at least a paragraph of text can take advantage of them. All three use the display property with a value of either block or inline (or inline-block, flow-root, or list-item, but we're not going to cover those).

Normal flow

Normal flow lays out content the same way that a word processor does - with lines of text organised into blocks (or paragraphs). The paragraph-like elements are known as block elements, and include < div >, < p >, < section >, < h1-6 > and any other element with display: block. The "text", on the other hand, consists of plain text content and elements with display: inline. Unsurprisingly, these elements are known as inline elements, and they're usually either text-formatting elements (like < strong > or < code >), or non-text content, (like < img > or < video >).

The block direction refers to the direction that block elements are placed on the page. For horizontally-written languages (like English, or Arabic) this is vertically down the page. For languages written vertically (like traditional Chinese), this is right to left across the page. However, languages which are traditionally written vertically on paper are usually written left-to-right on the web (see https://zh.wikipedia.org, https://www.nintendo.com/jp/index.html, https://www.joongang.co.kr/, etc), so, in practice, the block direction is almost always vertical.

The position of a block element in the block direction depends entirely on the size of its previous siblings, and the gaps between them (which are controlled by the margin-block or margin-top/margin-bottom properties). If the height of one block element changes, the positions of all the subsequent elements will be affected. This is the "flow" part of "normal flow".

The inline position of a block element is controlled by margin-inline-start or margin-left (margin-right for right-to-left-languages). A positive value will shift the element towards the end of the container, a negative value will shift it towards the start (and potentially out of the borders of the parent container). However, two block elements will never sit side-by-side, even if there's space for them to do so.

Figure

Fig. 1: The size and position of block elements can be adjusted, but they're always laid out one per row

Inline elements, on the other hand, are always laid out inside a block element, and they flow like words inside a paragraph - left-to-right or right-to-left across the page, wrapping to a new line as necessary. The direction they run in is referred to as the inline direction.

The mechanism that the browser uses to lay out inline content is called a linebox. Unfortunately, lineboxes aren't exposed in the dev tools, so we can't actually see one in the wild. But we can image them as tiny containers inside the block element, each holding a single row of text, as in Figure 2, below.

Fig. 2

Fig. 2: The dotted lines represent lineboxes, which the inline elements are laid out inside of

The width of a linebox is always the same width as the parent, and the height is controlled by the line-height property, which can either be set on the parent element, or on any of the children. When it's set on a child, it only affects the specific linebox that child is laid out in. When the parent and child have conflicting values, the larger value is used, as shown in Figure 3, below.

Fig. 3

Fig. 3: The parent has line-height: 60px, but the < span> containing the word "hat" has line-height: 80px, causing the first linebox to be larger than the rest.

It's a subtle distinction, but elements in a linebox are laid out relative to the linebox, not the parent. When elements are laid out relative to the parent (like block elements are), the vertical position can be adjusted using margins. But vertical margins (and paddings) have no effect on position inside a linebox, as shown in Figure 4, below.

Fig. 4

Fig: 4: The "hat" < span> has padding: 20px. While this affects its horizontal position, relative to the previous word, it has no effect on its vertical position, with the padding simply overflowing the linebox.

Instead, the vertical position is controlled by the vertical-align property, which comes with a few gotchas.

  • It only works on elements inside a linebox or table cell. It won't have any effect on a block element, and it won't work inside a grid or flex container.
  • The keyword values top, middle, bottom and baseline have no effect if the element is the same size as its linebox
  • Numeric values and the keywords sub and super always shift the element vertically, even if it's the same size as the linebox. The height of the linebox is adjusted to compensate.
  • Vertical positions are calculated based on font properties, like baseline and x-height, so they don't always line things up the way you might expect. If you're having trouble centre-aligning something, try applying vertical-align: middle` to all the elements in the linebox, or using flex or grid.

Multi-column layout

There are also a couple of ways to jazz up a text layout, while maintaining the convenience of normal flow. The first is multi-column layout, which produces newspaper-style columns.

Fig. 5

Fig. 5: Multi-column layout gives newspaper-style columns.

Multi-column layout is essentially normal flow, but with the lineboxes inside column containers inside the parent element. The child elements are still either block or inline, and follow the same rules as in normal flow.

Fig. 6

Fig. 6: Lineboxes (indicated by white dotted lines) inside column containers (indicated by blue dashed lines)

To use multi-column layout, we need to add a columns declaration to a block element parent. The value of columns can be either

  • the number of columns: columns: 4 produces four equal-width columns,
  • the size of the columns: columns: 200px produces as many columns as will fit in the container, where each column is at least 200px wide, or
  • both: columns: 4 200px will produces up to 4 columns, where each column is at least 200px wide.

It's also possible to style the gap between the columns, and have elements span all of the columns.

.columns {
 columns: 2 200px;
 column-gap: 20px;
 column-rule: 2px solid;
 img {
   column-span: all;
   display: block;
   width: 80%;
   margin-inline: auto;
   margin-block-start: 10px;
 }
}

Fig. 7

Fig. 7: Multicolumn layout with column...