back to module

Smashing Workshops

Normal Flow

Arrows navigate, cmd/ctrl-k for settings

CSS takes a source document organized as a tree of elements… and text nodes… and renders it onto a canvas such as your screen.

— CSS Display Module Level 4

CSS Layout starts with a source document (usually HTML), which is organized into a nested ‘tree’ of elements & text nodes.

Inspecting the page and hovering a text node in dev tools. It's not an element, but it acts like an inline element
The DOM Tree

This is the DOM tree, what we see when we open the web inspector. It’s based on our HTML, but might not match exactly, because it goes through a cleanup step. Here I’m hovering over a text node in the inspector, and it’s highlighted on the page.

The document canvas is the infinite surface over which the document is rendered.

— CSS Backgrounds and Borders

That document is rendered onto an infinite canvas.

Earth seen through a window on the international space station

But we interact with the document through our finite viewport. And that’s not really like viewing space through a window on the international space station, is it?

A viewport rectangle in the middle of a larger canvas, with a document anchored to the top left of the viewport, and content overflowing to the bottom and right.

Because the content from our document will move and re-size and try to fit inside whatever window we’re using!

Our viewport creates an ‘initial containing block’ which the document is placed into – anchored at one corner, and potentially overflowing at the opposite corner.

The pink box is now labeled
extrinsic size (imposed from outside),
and the oveflowing blue text
now has a dashed box and says
intrinsic size (from the content)

CSS layout is defined by this back-and-forth struggle between external context – how much extrinsic space is available – and internal content that needs a certain amount of space to render. It has some intrinsic size.

Context pushing in, and content pushing out – sometimes overflowing the container.

CSS is Rad: present, debug

Again, this awesome meme nails the fundamental problem of CSS. We have a limited viewport, and a whole document to fit inside it. How are we going to negotiate a solution that makes everything on the page accessible in the broad sense – something we can all access.

And like everything else in CSS, there’s a default setup in place – designed to be as flexible as possible.

Understanding Flow

Block and inline layout in normal flow

The default layout model is called flow or sometimes normal flow. But grid and flexbox and other layout modes also create a sort of document ‘flow’.

A page layout wireframe with arrows showing how each element pushes other elements out of the way.

The flow describes how our elements and text nodes share space in the container by default – starting in one corner and stacking in rows or columns or both. Each element taking up space, and pushing the elements next to it.

Text lines use inline axis [flow-inline: flow-inline]

Text stacks up in lines on the inline axis. For English text that’s horizontal, with lines flowing left to right.

Inline flow depends on language direction [flow-inline-rtl: flow-inline-rtl]

But that depends on the language direction. A right-to-left language (like Arabic) is still written in horizontal lines, but the direction is reversed (right to left).

Inline axis depends on writing mode [flow-inline-vert: flow-inline-vert]

And some languages are written in vertical lines, generally top to bottom. We call this the writing mode.

Blocks of text stack… [flow-block: flow-block]

When a line of text reaches the edge of the container, it fragments to create new lines – which stack together on the block axis.

For English this is vertical, with blocks stacking top-to-bottom.

Physical dimensions are static [Physical Dimensions: Physical Dimensions]

So in addition to our physical dimensions (width and height, which are fixed in place) we can talk about –

Logical dimensions are flow-relative [Logical Dimensions: Logical Dimensions]

The ‘flow-relative’ or ‘logical’ dimensions, which are based on the writing mode.

Logical (flow-relative) Sides: present, debug

We also get four logical ‘sides’. Top, right, bottom, and left are fixed in place – but block-start, block-end, inline-start, and inline-end orient us based on writing mode and direction.

The start or end of a line, and the start or end of a block.

flow-relative Logical Properties

CSS logical properties and values

Most web design is built around text, so we’re often making decisions relative to the flow. English sites often put the logo top left because that’s the start of our document flow – left to right, top to bottom.

There might be some exceptions. Mobile sites will sometimes put controls at the bottom of the page – not because of the document flow, but because that’s easier to reach when you’re holding a phone.

Logical properties: present, debug

Our job, as always, is to express what we mean as clearly as possible. The logical properties help us do that.

By using flow-relative properties our sites can re-orient efficiently without other changes. That’s useful even if we never translate our own site, because some browsers might translate for us!

Block & Inline Shorthands: present, debug

As a bonus, we get some useful shorthands for margin, padding, or borders on the inline or block axis. I use these all the time!

Default layout mode Normal Flow

Normal flow is designed to be invisible and obvious as a layout mode. It’s just the way we write documents.

CSS Reset (CSS Day)

There’s a great talk by the creators of CSS (Håkon Lie & Bert Bos) where they say “We didn’t think we needed to specify this,” because “everyone knows how a document works”. But let’s talk about how it works.

Two different ‘Normal’ Layout Modes

Because there are actually two different layout modes that are part of normal flow.

inline-boxes: permalink

Inline flow is made out of text nodes and boxes that flow in line with that text – and fragment at the edge of the container, wrapping to a new line.

Inline Flow: present, debug

Inline boxes get their inline size from contents, and their block size from the container – from the height of the line. If we add content, the inline size grows to contain it, even if that means wrapping.

We’re not allowed to set an explicit size on inline elements. We can add border/padding/margins, but they only impact the inline-flow – they don’t change the size of the line itself.

block-boxes: permalink

Block flow requires everything to stack on the block axis. There’s no wrapping at a container edge, there’s just the stack.

Block Flow: present, debug

Block boxes get their block size from contents, and their inline size from the container. If we add more contents, the block-size will grow to contain it.

Block boxes can be given an explicit size, but even making them narrow doesn’t change the stack. Boxes don’t move up to fit in empty spaces.

Inline Splitting: present, debug

Block and inline flow don’t really mix. Which one we get is determined by what content we have. If all the siblings are text or inline elements, we get inline flow. But as soon as we put a block in there, browsers split the inline parts into ‘anonymous’ boxes on either side.

Primary axis Sized By Contents

Intrinsic Size
  • So for both block and inline elements
  • The primary axis comes from content
  • This is the intrinsic size
  • To find it, look inside at children
  • On block elements, the block axis
  • On inline elements, the inline axis

Cross axis Sized By Context

Extrinsic Size
  • And in both cases,
  • the cross-axis is sized by context
  • This is extrinsic sizing
  • And we find it by looking outward

Length values Provide Extrinsic Sizes

  • Extrinsic sizes often come from the parent
  • But they can also come from our styles
  • Any size that we set explicitly using length units
  • (width, height, padding, margins, whatever)
  • will be extrinsic
  • It doesn’t come from the natural size of the content,
  • But it’s imposed from the outside

All CSS Units

Including % and other ‘relative’ units

It doesn’t matter what units we’re using.

This includes % and em and vw and other relative units.

While they are relative to other aspects of the design, they are still imposed from outside. But we do have a few…

Intrinsic Sizing Keywords: present, debug

Boxes don’t just have one intrinsic size, they often have a minimum, and maximum – depending on how content is wrapped. The auto behavior for a block box is the container size, clamped at the low end by the min-content size.

We have keywords to choose one of these intrinsic sizes, or change the clamping between min and max with fit-content. Replaced elements like images and video can also have a ‘natural size’ that comes from the source file.

Intrinsic Size Keywords

min-content | max-content | fit-content

  • Intrinsic keywords that we can use as well
  • Min-content, max-content, and fit-content

display… inline or block

Outer display modes

We’re all familiar with these two display modes, inline and block, right?

These are called ‘outer’ display modes. They change how the outside of the item behaves in a normal flow context.

Outside normal flow… Everything is Block-ified

As soon as the element is removed from normal flow, there’s no difference. Take a span or div and put it anywhere else – grid, flexbox, absolute positioning, even floats – and they’ll act identical.

Grid items aren’t inline or block, they’re just grid items. The outer display is ignored.

Also display… Inner Layout Mode

‘normal’ flow, multi-column flow, grid, flexbox, tables, etc.

‘Formatting Context’

But the display property can also be used to control the inner layout mode of an element – the formatting context that is used to lay out children.

display: flow | flow-root

Display-inside (MDN)

That includes the options to explicitly create a flow or flow-root context.

display-flow-root

Browser support for display-flow-root

These are kinda new, but they’ve been Baseline since 2020.

block ➡️ block flow
inline ➡️ inline flow

The reason you rarely see them is because they are the default inner display mode. If you set display: block or display: inline, that implies you want more flow inside the element.

It’s the same as writing display: block flow or display: inline flow.

Two Value Display

Browser support for Two Value Display

That’s right, we can set two values for the display property! But usually we don’t have to, because the implied default is correct.

flow ➡️ block flow
grid ➡️ block grid

It works the other way too. If we only set an inner display mode, the outer default is block. If we ask for flow inside, we get a block box to contain that flow. If we ask for grid or flex or something else inside, we also get a block box to contain it.

Except when we need… inline-* / inline *

But sometimes we do need a special combo, where we get an inline box with something different inside! That’s why we have options like inline-block, inline-grid, and inline-flex.

inline-flex ➡️ inline flex
inline-grid ➡️ inline grid

It’s fine to use the one-word hyphenated versions of those, but the Working Group recommends moving away from that syntax, and using two words instead.

There’s an active debate arguing if new layouts should even get the one-word option.

inline-block ➡️ inline flow-root

The strange case here is the oldest one. With inline-block, what we want is not just an inline-flow (that’s the default) – we need an inline flow root.

Inline box size (live): present, debug

While this is still behaving like an inline element externally, sitting on a line of text, we can give it explicit sizes, and margins or padding or borders that impact the line.

flow-root contains…

“A mini layout in your layout”

Flow-root is not just creating a flow context, but isolating that flow from outside interactions. It’s “a mini layout inside your layout”.

Flow-Root Block Formatting Context

Rachel Andrew, Understanding CSS Layout And The Block Formatting Context

Technically this is called a Block Formatting Context. Or what IE6 used to call hasLayout, if you remember that. It’s the same thing we need to create for a clearfix to wrap around floated elements. Do the kids these days still know about the clearfix? Well it’s a lot easier now. Display flow-root is all you need.

Rachel Andrew, Understanding CSS Layout And The Block Formatting Context: present, debug
  • Rachel Andrew has an excellent article
  • With various demos to show what a BFC does
  • Keeping floats contained
Rachel Andrew, Understanding CSS Layout And The Block Formatting Context: present, debug
  • Keeping external floats out
Rachel Andrew, Understanding CSS Layout And The Block Formatting Context: present, debug
  • The same with margins
  • Don’t interact or collapse across the boundary

display property Box Generation Values

There are a few more things the display property can do. It really is overloaded, and I think we should treat it as a shorthand.

In addition to inner and outer display values, we can also remove the element box entirely, or generate extra boxes.

Each element Generates One Box

(by default)

By default every element on the page generates a single box.

display: none Removes Box Tree

Content is also removed

Display none will remove the box and everything inside it. Nothing is rendered.

display: contents Removes Box

Content remains

Display contents removes the box, but leaves everything inside. This can be helpful in some situations, but we have to be careful…

Historically… ⚠️ Severe A11y Issues

Display contents (see notes)

This can cause major accessibility issues if we’re not testing. Much of that has been fixed, but not everyone is on the latest browsers, and…

Never use… ⚠️ On Buttons & Tables

Display contents (see notes)

Browsers still have issues on HTML buttons and tables. So never use display contents on those elements.

I’d also be cautious with anything interactive, or where the semantics matter. I mostly use this for removing unnecessary wrapper divs, that come from a JS framework or somewhere else.

display: list-item Adds Marker Box

Outside or inside the principal box

Finally, the list-item display generates an additional ::marker box – which we can move in or out of the principal box using list-style-position.

Markers & counters: present, debug

We can style that box to some extent, using the ::marker pseudo-element.

box-sizing property… Selects Box Edge for Sizing

But let’s get back to layout, and a very useful little property – box-sizing – which I think is over-used and under-used at the same time.

Diagram of the CSS box model, with arrows to each box-edge: from an inner content-box edge around the content area, then (working out) a padding-box edge around the padding area, a border-box edge, and a margin-box edge
Box Model Edges

If we’re setting an explicit size on something, we need to decide what size we care about – the outside edge of the border, or the outside-edge of the contents.

Diagram of an element box, with content, padding, border, and margin -- width and height labels are applied to the content only
The content-box model

I think the default here – content-box sizing – makes a lot of sense if we’re setting up a container, and we care about what fits inside.

If we add padding and border to that, we’re not taking anything away from the content – it just makes the container larger.

Diagram of an element box, with content, padding, border, and margin -- width and height labels are applied to everything except the margin
The border-box model

But if we’re trying to lay items out on a grid, then border-box sizing makes more sense. What we care about is how the borders line up, so we set the outside of the box, then subtract padding & borders to get the available content size.

Neither is Better

Inside or outside dimensions?

Neither of those is better all around, it just depends if you’re worried about the inside dimensions of the box, or the outside dimensions.

css
* { box-sizing: border-box; }

But it used to matter! Because we were setting sizes on everything to create a grid out of floated elements. So people got in a habit of setting it globally as part of the reset styles.

Matters less With Flexbox and Grid

Only set it when it matters…

That’s fine, but I think most sites could remove it now, without even noticing. I tried pulling it out of some large projects, and basically nothing broke.

With flexbox and grid, we’re just not setting explicit sizes on everything.

css
.container {
  inline-size: calc(34em + (var(--padding) * 2));
}
Use content-box sizing instead…

The clue that you need to change models is any time you find yourself adding or subtracting the padding and borders from a width. If you’re adding them to get an outer width, use content-box –

css
.container {
  inline-size: calc(34em - (var(--padding) * 2));
}
Use border-box sizing instead…

And if you’re removing them to get an inner width, use border-box instead.

Next module… Grids & Alignment

« back to resources
SlideDeck Controls

View

Keyboard shortcuts
cmd/ctrl-k:
Toggle this control dialog
cmd/ctrl-shift-enter:
presentation (from first slide)
cmd/ctrl-enter:
presentation (from active slide)
alt-enter:
in speaker view (following active slide)

Color Settings