align-content works in the default layout in 2024, allowing vertical centering with 1 CSS property.

<div style="align-content: center; height: 100px;">
  <code>align-content</code> just works!
</div>
align-content just works!

Supported since:
Chrome: 123 | Firefox: 125 | Safari: 17.4

What’s new?

The status quo for CSS alignment is to switch to flexbox or grid because align-content doesn’t work in the default layout (flow). In 2024, browsers have implemented align-content for flow layout. This has some advantages:

  • You do not need flexbox or grid, just 1 CSS property for alignment.
  • Therefore, the content doesn’t need to be wrapped in a div.
<!-- Works -->
<div style="display: grid; align-content: center;">
  Content.
</div>
<!-- FAIL! -->
<div style="display: grid; align-content: center;">
  Content with <em>multiple</em> nodes.
</div>
<!-- Works with the content wrapper -->
<div style="display: grid; align-content: center;">
  <div>  <!-- The extra wrapper -->
    Content with <em>multiple</em> nodes.
  </div>
</div>
<!-- Works without the content wrapper -->
<div style="align-content: center;">
  Content with <em>multiple</em> nodes.
</div>

It’s amazing that CSS finally has a single property to control vertical align after decades of progress!

Vertical centering — a history

Browsers are funny, basic needs like aligning stuff do not have simple answers for a very long time. Here is how to center stuff in libreoffice:

Here is how to center vertically in a browser (horizontal centering is another topic):

Method 1: table cell

Sanity: ★★★☆☆

There are 4 major layouts: flow (default), table, flexbox, grid. How to align stuff depends on the layout of the container. Flexbox and grid were added rather late, so table was the first option.

<div style="display: table;">
  <div style="display: table-cell; vertical-align: middle;">
    Content.
  </div>
</div>

A table can be summoned purely from CSS, but it’s a shame that such an indirection was needed.

Method 2: absolute positioning

Sanity: ☆☆☆☆☆

For reasons I don’t understand. People kept inventing more indirect ways to do things.

<div style="position: relative;">
  <div style="position: absolute; top: 50%; transform: translateY(-50%);">
    Content.
  </div>
</div>

This one uses absolute positioning to bypass the layout, since the flow layout doesn’t help us:

  1. Mark the reference container with position: relative.
  2. Place the edge of the content at the center with position: absolute; top: 50%.
  3. Offset the content center to the edge with transform: translateY(-50%).

Method 3: inline content

Sanity: ☆☆☆☆☆

While the flow layout doesn’t help with content alignment. It allows vertical alignment within a line. So why not make a line as tall as the container?

<div class="container">
  ::before
  <div class="content">Content.</div>
</div>
.container::before {
  content: '';
  height: 100%;
  display: inline-block;
  vertical-align: middle;
}
.content {
  display: inline-block;
  vertical-align: middle;
}

This has some flaws: besides sacrificing a pseudo-element, there’s a zero-width “strut” character at the beginning that can mess stuff up.

Method 4: single-line flexbox

Sanity: ★★★☆☆

Flexbox became widely available 2 decades after the Web has taken off. It has 2 modes: single-line and multi-line. In single-line mode (default), the line fills the vertical space, and align-items aligns stuff inside the line.

<div style="display: flex; align-items: center;">
  <div>Content.</div>
</div>

Alternatively, make the line columnar and align items with justify-content.

<div style="display: flex; flex-flow: column; justify-content: center;">
  <div>Content.</div>
</div>

Method 5: multi-line flexbox

Sanity: ★★★☆☆

In a multi-line flexbox, the line no longer fills the vertical space, so the line (with just 1 item in it) can be aligned with align-content.

<div style="display: flex; flex-flow: row wrap; align-content: center;">
  <div>Content.</div>
</div>

Method 6: grid content

Sanity: ★★★★☆

Grid was even later. Alignment became simpler.

<div style="display: grid; align-content: center;">
  <div>Content.</div>
</div>

Method 7: grid cell

Sanity: ★★★★☆

Note the subtle difference from the previous one:

  • align-content centers the cell to the container.
  • align-items centers the content to the cell while the cell stretches to fit the container.
<div style="display: grid; align-items: center;">
  <div>Content.</div>
</div>

There seems to be many ways to do the same thing.

Method 8: auto-margin

Sanity: ★★★☆☆

In flow layout, margin:auto centers horizontally, but not vertically. Flexbox and grid do not share this absurdity.

<div style="display: grid;">
  <div style="margin-block: auto;">
    Content.
  </div>
</div>

Still, it puzzled me why margin was designed to also control alignment.

Method 9: this post in 2024

Sanity: ★★★★★

Why didn’t browsers add this in the first place?

<div style="align-content: center;">
  <code>align-content</code> just works!
</div>

The table cell from the Method 1, like this method, also doesn’t require a content wrapper (it requires a table wrapper, though). We’re back to square one!

Summary

All vertical centering methods in this codepen. Know other methods? Feel free to tell me.

Going 2-dimensional

Is there a single property for horizontal align? What’s the counterpart of align-content? Let’s take a look at various alignment properties:

Table: alignment properties in different layouts
flow flexbox grid
  align-content block axis cross axis (line) block axis (grid)
justify-content no effect main axis inline axis (grid)
  align-items no effect cross axis (item) block axis (cell)
justify-items no effect no effect inline axis (cell)

Background: CSS axis terminology

The block axis is usually vertical, and the inline axis is horizontal. These terms are needed because vertical writing-mode is a thing, so block axis and inline axis are relative to the text direction. This is similar to how main axis and cross axis are relative to the flexbox item direction.

inline axis b l o c k a x i s block axis i n l i n e a x i s 1 3 2 main axis 4 c r o s s a x i s text direction (writing-mode) flexbox direction

On naming things

From the names of properties we can infer how CSS is designed:

  • align-* is mostly vertical, while justify-* is mostly horizontal.
  • *-content and *-items control different levels of objects?

justify-content is the counterpart of align-content, which is handy in grid layout but has no effect in flow layout. The place-content shorthand sets both.

“Align” vs. “justify”

Why do “align” and “justify” refer to axes in CSS? Is justify-* inspired by text justification? It’s chaotic, considering there’s also text-align: justify.

Usually when people say “align”, they mean the placement of a single object, while “justify” means the distribution of multiple objects.

While in CSS, both justify-* and align-* are like text justification, because they accept values like space-between; they just mean different axes!

How to memorize: Text justification is horizontal, so is justify-*.

“Content” vs. “items”

In flexbox, “content” and “items” are confusing:

  • Main axis: justify-content controls items, while justify-items has no effect.
  • Cross axis: differences between single-line and multi-line modes.
flexbox justify-content justify-items grid ?

Conclusion: “items” is for stuff that can be aligned individually. On the main axis, flex items cannot be aligned individually, so it’s “content”.

Why is CSS so confusing?

Even if we ignore historical artifacts, CSS is still too confusing for most of us. It has hundreds of poorly named properties, each can influence the outcome in unintuitive ways.

Software projects gone haywire

This is a case study in software design paradigms:

  • Unix: orthogonal, composable primitives that can be reasoned about independently.
  • CSS (Central Software System): just amend the software with more and more knobs.

The early WWW was just linked documents. CSS was created to style documents without regard to layout. Over time, CSS gained some random layout features without a coherent vision.

Often you have many ways to do something in CSS, but not the right feature to do it sanely. This entire post is about a new feature for saner vertical align, and the horizontal axis is still different.

In contrast, libreoffice follows the paradigm of orthogonal, composable primitives:

  • Alignment is unified. No point that vertical is different from horizontal.
  • Unified alignment is possible because “align” and “justify” are orthogonal, not mixed up.
    • “Align” is a property of the container.
    • “Justify” is a property of the paragraph.
  • “Align” and “justify” can be combined in any way.
libreoffice align libreoffice justify

CSS mastery takes effort!

Still, rules can be learned, even for something as incomprehensible as CSS. You just need to pay extra attention instead of relying on trial-and-error and copy-pasting. I’m creating a visual guide to the hard parts of CSS. Check it out!

A Visual Guide To CSS