CSS Module 10 – Flexbox
Flex alignment, auto margins and order
Align one flex item with align-self and wrapped lines with align-content, place items with auto margins, and reorder visually without breaking focus order.
What you will learn
- Use align-self for single items and align-content for wrapped lines
- Use auto margins to place and separate items precisely
- Change the visual order without breaking keyboard and reading order
Before you start
What you’ll learn · Before you start
What you will learn
- Use align-self for single items and align-content for wrapped lines
- Use auto margins to place and separate items precisely
- Change the visual order without breaking keyboard and reading order
Before you start
On this page
Flexbox aligns along both of its axes. Across the line, align-items gives every item its default position,
align-self changes it for one item, and align-content moves the lines themselves when a container has wrapped
onto more than one. Along the line, auto margins take the free space before justify-content does, so a single
margin-inline-start: auto pushes an item to the end, and auto margins on both sides centre it. The order property
and the -reverse directions change where items are drawn, but not the order in which a keyboard or a screen reader
reaches them, so a careless reorder can fail two WCAG 2.2 success criteria, 1.3.2 and 2.4.3. This lesson measures
what Chromium 153, Firefox 155 and WebKit 26.6 did with each case, down to where the Tab key moved focus.
One item out of line: align-self
align-self takes the same values as align-items, but on a single item, where it overrides the container’s
setting. Its default, auto, means “do what align-items says”. In a two-line container where every item stretches
to its 100 px line, one item with align-self: flex-end kept its own 24 px height and sat at the bottom of its line,
76 px down, in all three engines. A common use is a button in a column layout: the items of a column stretch across
its width by default, and align-self: flex-start keeps the button as wide as its text.
Lines, not items: align-content
align-content works one level up. It distributes the space between the lines of a multi-line container, with
the same values as justify-content plus stretch, while align-items and align-self place each item inside its
own line. A 300 by 200 px container with items 120 px wide and 24 px tall (as tall as their text); the last word in
the first column is the align-content value:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>align-content moves lines, align-items moves items</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.box { display: flex; inline-size: 300px; block-size: 200px; margin-block-end: 8px; background: #eee; }
.box > div { inline-size: 120px; background: #e6edfc; } /* no height: 24px of text, unless stretched */
.wrap { flex-wrap: wrap; }
#nowrap-center { align-content: center; } /* one line that can't wrap: align-content does nothing */
#one-line-center { align-content: center; } /* can wrap, has one line: the line is centred */
#two-lines-center { align-content: center; }
#two-lines-between { align-content: space-between; }
#two-lines-self .last { align-self: flex-end; } /* one item, at the bottom of its own line */
</style>
</head>
<body>
<div class="box" id="nowrap-center"><div>1</div><div>2</div></div>
<div class="box wrap" id="one-line-normal"><div>1</div><div>2</div></div>
<div class="box wrap" id="one-line-center"><div>1</div><div>2</div></div>
<div class="box wrap" id="two-lines-normal"><div>1</div><div>2</div><div>3</div><div>4</div></div>
<div class="box wrap" id="two-lines-center"><div>1</div><div>2</div><div>3</div><div>4</div></div>
<div class="box wrap" id="two-lines-between"><div>1</div><div>2</div><div>3</div><div>4</div></div>
<div class="box wrap" id="two-lines-self"><div>1</div><div class="last">2</div><div>3</div><div>4</div></div>
<script>
for (const box of document.querySelectorAll('.box')) {
const top = box.getBoundingClientRect().y;
const items = [...box.children].map((d) => {
const r = d.getBoundingClientRect();
return `${d.textContent}: y ${r.y - top}, ${r.height} tall`;
});
console.log(`${box.id} | ${items.join(' | ')}`);
}
</script>
</body>
</html> | Container | Lines | Items |
|---|---|---|
nowrap, center |
one of 200 px | 200 px tall |
wrap, one line, normal |
one of 200 px | 200 px tall |
wrap, one line, center |
one of 24 px, at 88 | 24 px tall |
wrap, two lines, normal |
two of 100 px | 100 px tall |
wrap, two lines, center |
two of 24 px, at 76 and 100 | 24 px tall |
wrap, two lines, space-between |
at 0 and 176 | 24 px tall |
All three engines gave these numbers. The first two rows hold the classic trap: align-content has no effect on a
container that cannot wrap, because its only line is always as tall as the container. Once flex-wrap: wrap is set,
it works even while everything still fits on one line. The default, normal, behaves like stretch: the lines share
the spare height and stretchy items grow with them, which is why two wrapped lines of short items suddenly look
100 px tall.
align-content moves lines, align-items moves items
Text description of the diagram
The diagram shows a wrapping flex container 300px wide and 200px tall with four items, 120px wide and 24px tall when nothing stretches them, on two lines. It is drawn at 80 % scale.
- With align-content: normal, which behaves as stretch, the two lines share the container's height, so each line is 100px tall, and the items, which have align-items: stretch by default, fill their line and become 100px tall.
- With align-content: center, each line is only as tall as its items, 24px, and the two lines are packed together in the middle, with 76px of free space above them and 76px below.
align-content moves and sizes the lines; align-items and align-self place each item inside its own line.
Text on one baseline
align-items: baseline lines up the first line of text in each item, whatever the font sizes. A section heading in
32 px type with a link in 16 px type beside it, first centred and then on the baseline:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A heading and a link on one baseline</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.section-head { display: flex; gap: 1rem; margin-block-end: 16px; }
.section-head h2 { margin: 0; font-size: 2rem; line-height: 1.25; }
.section-head a { margin-inline-start: auto; }
#centred { align-items: center; }
#baseline { align-items: baseline; }
.mark { display: inline-block; inline-size: 0; block-size: 0; } /* sits on the text's baseline */
</style>
</head>
<body>
<div class="section-head" id="centred"><h2><span class="mark"></span>Plans</h2><a href="#compare"><span class="mark"></span>Compare all plans</a></div>
<div class="section-head" id="baseline"><h2><span class="mark"></span>Plans</h2><a href="#compare"><span class="mark"></span>Compare all plans</a></div>
<script>
// An empty inline-block's bottom edge is the baseline of its line, so it shows where the text sits.
for (const head of document.querySelectorAll('.section-head')) {
const top = head.getBoundingClientRect().y;
const [h2, link] = [...head.querySelectorAll('.mark')].map((m) => +(m.getBoundingClientRect().bottom - top).toFixed(2));
console.log(`${head.id}: heading baseline at ${h2}px, link baseline at ${link}px, ${+(h2 - link).toFixed(2)}px apart`);
}
</script>
</body>
</html> Centred, the heading’s text sat on a baseline 31 px down and the link’s 25 px down in Chromium, 6 px apart (5.5 px in
Firefox and WebKit), so the link looked as if it were floating. With align-items: baseline both baselines were at
31 px (31.09 px in WebKit). last baseline aligns the last lines of text instead, for example a button that should
sit on the last line of the paragraph beside it; it is Baseline widely available, from Chrome 108, Firefox 52 and
Safari 16.2. Items aligned on a baseline are not stretched.
Auto margins: the free space goes to them first
An auto margin on a flex item absorbs free space along its axis, and it does so before justify-content
distributes anything. If one auto margin on a line takes all the free space, justify-content has nothing left to
share. Three 80 px items in a 400 px row, 160 px of free space. “Start of c” means margin-inline-start: auto on c,
and the second row adds justify-content: center:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Auto margins and overflow</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.box { display: flex; inline-size: 400px; block-size: 120px; margin-block-end: 8px; background: #eee; }
.box > div { flex: none; inline-size: 80px; block-size: 40px; background: #e6edfc; }
#push-last .c { margin-inline-start: auto; }
#push-last-centre { justify-content: center; } /* has nothing left to share */
#push-last-centre .c { margin-inline-start: auto; }
#both-sides .b { margin-inline: auto; }
#split .a { margin-inline-end: auto; }
#split .c { margin-inline-start: auto; }
#centre-both-axes > div { margin: auto; }
/* An item wider than its 200px container, in a container that scrolls. */
.narrow { inline-size: 200px; overflow-x: auto; }
.narrow > div { inline-size: 300px; }
#overflow-center { justify-content: center; }
#overflow-safe { justify-content: safe center; }
#overflow-margins > div { margin-inline: auto; }
#overflow-around { justify-content: space-around; }
</style>
</head>
<body>
<div class="box" id="push-last"><div class="a">a</div><div class="b">b</div><div class="c">c</div></div>
<div class="box" id="push-last-centre"><div class="a">a</div><div class="b">b</div><div class="c">c</div></div>
<div class="box" id="both-sides"><div class="a">a</div><div class="b">b</div><div class="c">c</div></div>
<div class="box" id="split"><div class="a">a</div><div class="b">b</div><div class="c">c</div></div>
<div class="box" id="centre-both-axes"><div>a</div></div>
<div class="box narrow" id="overflow-center"><div>wide</div></div>
<div class="box narrow" id="overflow-safe"><div>wide</div></div>
<div class="box narrow" id="overflow-margins"><div>wide</div></div>
<div class="box narrow" id="overflow-around"><div>wide</div></div>
<script>
for (const box of document.querySelectorAll('.box')) {
const b = box.getBoundingClientRect();
const items = [...box.children].map((d) => {
const r = d.getBoundingClientRect();
return `${d.textContent} at x ${+(r.x - b.x).toFixed(2)}, y ${+(r.y - b.y).toFixed(2)}`;
});
const scroll = box.classList.contains('narrow') ? `; scrolls over ${box.scrollWidth}px` : '';
console.log(`${box.id}: ${items.join(', ')}${scroll}`);
}
</script>
</body>
</html> | Auto margins | a | b | c |
|---|---|---|---|
| start of c | 0 | 80 | 320 |
start of c, center |
0 | 80 | 320 |
| both sides of b | 0 | 160 | 320 |
| end of a, start of c | 0 | 160 | 320 |
The x positions were identical in all three engines. Two auto margins share the free space equally, 80 px each,
whether they belong to one item or to two. They work on the cross axis too: a single item with margin: auto in a
400 by 120 px container sat at x 160 and y 40, centred in both directions without any alignment property.
Pinning a button to the bottom of a card
Cards in a row stretch to the height of the tallest, but their contents do not: a short description leaves its button halfway up the card. Make each card a column flex container, and give the button an auto margin on top:
/* Cards of equal height in a row, each with its button on the bottom edge however long its text is. */
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.card {
display: flex;
flex-direction: column; /* the card's own main axis is vertical */
flex: 1 1 12rem;
padding: 1rem;
}
.card p {
margin-block: 0 1rem;
}
.card .button {
margin-block-start: auto; /* takes all the free space above the button */
align-self: flex-start; /* as wide as its text, not the whole card */
} <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Buttons on the bottom edge of every card</title>
<link rel="stylesheet" href="card-footer.css">
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.card { background: #e6edfc; }
.cards { margin-block-end: 16px; }
#no-auto .button { margin-block-start: 0; } /* the same cards without the auto margin */
</style>
</head>
<body>
<div class="cards" id="auto">
<article class="card"><h3>Starter</h3><p>For one person.</p><a class="button" href="#starter">Choose Starter</a></article>
<article class="card"><h3>Team</h3><p>For up to ten people, with shared folders, comments on every file and a history of each change.</p><a class="button" href="#team">Choose Team</a></article>
<article class="card"><h3>School</h3><p>For classes, with a teacher's view.</p><a class="button" href="#school">Choose School</a></article>
</div>
<div class="cards" id="no-auto">
<article class="card"><h3>Starter</h3><p>For one person.</p><a class="button" href="#starter">Choose Starter</a></article>
<article class="card"><h3>Team</h3><p>For up to ten people, with shared folders, comments on every file and a history of each change.</p><a class="button" href="#team">Choose Team</a></article>
<article class="card"><h3>School</h3><p>For classes, with a teacher's view.</p><a class="button" href="#school">Choose School</a></article>
</div>
<script>
for (const cards of document.querySelectorAll('.cards')) {
const top = cards.getBoundingClientRect().y;
const rows = [...cards.children].map((card) => {
const c = card.getBoundingClientRect();
const b = card.querySelector('.button').getBoundingClientRect();
return `${card.querySelector('h3').textContent}: card ${+c.height.toFixed(2)}px tall, button bottom ${+(b.bottom - top).toFixed(2)}`;
});
console.log(`${cards.id} at ${innerWidth}px | ${rows.join(' | ')}`);
}
</script>
</body>
</html> At 1024 px the three cards were all 205.44 px tall in Chromium and WebKit (205.43 px in Firefox), and with the auto margin every button’s bottom edge was at 189.44 px, the card’s 16 px of padding above its bottom edge. Without it, the buttons of the two short cards ended at 141.44 px, 48 px higher than the Team card’s. At 360 px the cards stack, one per line, each as tall as its own content, so the two versions look the same; the auto margin only does work when there is spare height.
When items overflow: safe and unsafe centring
Centring has a dangerous side. When an item is larger than its container, center keeps it centred, so it hangs over
both edges, and the part over the start edge may be impossible to reach. A 300 px item in a 200 px container that
scrolls, aligned four ways (three values of justify-content, and auto margins on the item):
| Alignment | Starts at | Start reachable |
|---|---|---|
center |
−50 px | no (WebKit: yes) |
safe center |
0 | yes |
margin-inline: auto |
0 | yes |
space-around |
0 | yes |
With plain center, Chromium and Firefox could not scroll to the first 50 px of the item at all; WebKit let the
scroll position go to −50 px and showed them.
The safe keyword means “centre, unless that would push content past the start edge; then start”. It is Baseline
newly available for flexbox: Chrome 115, Firefox 63 and Safari 17.6 support it. Auto margins have always behaved this
way, because they only take positive free space, so they are the safe centring that every browser handles. The last
row shows the specification’s newer rule: when items overflow, space-between falls back to safe flex-start, and
space-around and space-evenly to safe center, and all three engines followed it. The real-world version of this
bug is a dialog centred in a full-screen flex container: on a short phone screen in landscape, its top, often with
the title and the close button, ends up above the screen.
Visual order and keyboard order
The order property takes an integer, 0 by default, and the container draws its items sorted by it, keeping the HTML
order between items with equal values. flex-direction: row-reverse draws them from the end of the line instead.
Neither changes the order in which the Tab key moves focus, or in which a screen reader reads, and that is easy to
measure:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Visual order and keyboard order</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.toolbar { display: flex; gap: 8px; margin-block-end: 8px; }
#moved .help { order: -1; } /* drawn first, still last in the HTML */
#reversed { flex-direction: row-reverse; } /* drawn from right to left */
#reading-flow { reading-flow: flex-visual; } /* limited availability: Chromium only */
#reading-flow .help { order: -1; }
</style>
</head>
<body>
<div class="toolbar" id="moved"><button type="button">New</button><button type="button">Open</button><button type="button">Save</button><button type="button" class="help">Help</button></div>
<div class="toolbar" id="reversed"><button type="button">New</button><button type="button">Open</button><button type="button">Save</button><button type="button" class="help">Help</button></div>
<div class="toolbar" id="reading-flow"><button type="button">New</button><button type="button">Open</button><button type="button">Save</button><button type="button" class="help">Help</button></div>
<script>
// The order a sighted reader sees: left to right on the screen.
for (const bar of document.querySelectorAll('.toolbar')) {
const seen = [...bar.children].sort((a, b) => a.getBoundingClientRect().x - b.getBoundingClientRect().x);
console.log(`${bar.id}: on screen ${seen.map((b) => b.textContent).join(', ')}`);
}
</script>
</body>
</html> Pressing Tab through the page, one button at a time (Option+Tab in WebKit: in Safari, plain Tab reaches only fields and pop-up menus until a setting is changed, as Apple’s guide to its keyboard shortcuts says):
| Toolbar | On screen | Keyboard |
|---|---|---|
order: -1 on Help |
Help, New, Open, Save | New, Open, Save, Help |
row-reverse |
Help, Save, Open, New | New, Open, Save, Help |
order: -1 and reading-flow |
Help, New, Open, Save | Help first only in Chromium |
The first two keyboard orders were the same in all three engines. With reading-flow: flex-visual, Chromium
followed the drawn order, Help, New, Open, Save, while Firefox and WebKit kept the HTML order. In the first two
toolbars a keyboard user meets Help last although it is drawn first, and in the reversed toolbar the
focus ring travels from right to left. WCAG 2.2 has two Level A success criteria for this. 1.3.2, Meaningful
Sequence, asks that when the order of content matters for its meaning, the correct reading order can be determined
from the code, and 2.4.3, Focus Order, asks that keyboard focus moves in an order that keeps the page’s meaning and
operation. The flexbox specification itself says authors must not use order or the -reverse values in place of a
correct source order.
The reading-flow property of CSS Display Level 4 can make focus follow the drawn order, as the third toolbar
shows, but it has limited availability: only Chrome and Edge 137 and later support it, and in Firefox and WebKit the
toolbar behaved like the first one. The check is simple enough to automate:
// The order a flex container draws its items in is the "order-modified document order" of CSS Display Level 3: the
// items sorted by their order value, with ties kept in HTML order. row-reverse then lays that list out from the end
// of the line. Focus and screen readers keep to the HTML order, so this program flags every toolbar whose drawn order
// differs. The toolbars are the first two of order.html; Chromium 153, Firefox 155 and WebKit 26.6 drew them the same.
function drawnOrder(names, { order = {}, reverse = false } = {}) {
const sorted = names
.map((name, html) => ({ name, html, order: order[name] ?? 0 }))
.sort((a, b) => a.order - b.order || a.html - b.html)
.map((item) => item.name);
return reverse ? sorted.reverse() : sorted; // left to right on screen
}
const html = ['New', 'Open', 'Save', 'Help'];
const toolbars = [
{ id: 'moved', css: '.help { order: -1 }', order: { Help: -1 } },
{ id: 'reversed', css: 'flex-direction: row-reverse', reverse: true },
{ id: 'fixed', css: 'no order: Help moved first in the HTML', html: ['Help', 'New', 'Open', 'Save'] },
];
for (const bar of toolbars) {
const source = bar.html ?? html;
const screen = drawnOrder(source, bar);
const same = screen.join() === source.join();
console.log(`${bar.id} (${bar.css})`);
console.log(` on screen: ${screen.join(', ')}`);
console.log(` keyboard: ${source.join(', ')}`);
console.log(` ${same ? 'the same order: nothing to fix' : `differs: ${screen[0]} is drawn first but reached ${source.indexOf(screen[0]) + 1}${['st', 'nd', 'rd'][source.indexOf(screen[0])] ?? 'th'} by the keyboard`}`);
} Output
moved (.help { order: -1 })
on screen: Help, New, Open, Save
keyboard: New, Open, Save, Help
differs: Help is drawn first but reached 4th by the keyboard
reversed (flex-direction: row-reverse)
on screen: Help, Save, Open, New
keyboard: New, Open, Save, Help
differs: Help is drawn first but reached 4th by the keyboard
fixed (no order: Help moved first in the HTML)
on screen: Help, New, Open, Save
keyboard: Help, New, Open, Save
the same order: nothing to fix
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node order_check.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
The fix that works everywhere is the one in the third case of the program: put the items in the HTML in the order
people should meet them, and keep order and -reverse for changes that carry no meaning, such as which side of a
decorative image a caption sits. WCAG’s technique C27 describes exactly this: make the order of the DOM match the
visual order.
Practise: place items with auto margins and justify-content
Exercise · Medium · JavaScript
Place items on a line with auto margins and justify-content
Write place(lineSize, items, justify) in place.mjs and export it. It returns the start position (x, in px) of each item on one flex line lineSize px long, the way a browser places flex items along the main axis once their sizes are known. Each item is { size }, and may have autoStart: true or autoEnd: true for an auto margin on that side; every other margin is 0 and there is no gap. justify is one of flex-start (the default), center, flex-end, space-between, space-around, space-evenly and safe center.
1. The free space is lineSize minus the sizes of the items. 2. If the free space is positive and at least one auto margin exists, every auto margin gets an equal share of it, and justify has nothing left to distribute. 3. Otherwise auto margins are 0 and justify places the items: center puts half the free space before the first item, flex-end all of it; space-between shares it between the items, space-around gives each item half a share on both sides, and space-evenly makes every space, including the two ends, equal. 4. When the free space is negative (the items overflow), space-between, space-around and space-evenly start the items at 0, and so does safe center, while plain center lets them hang over both edges. With a single item, space-between starts it at 0.
The sample tests import place from place.mjs and run in your browser. Their numbers are the positions Chromium 153, Firefox 155 and WebKit 26.6 drew for the lesson's examples.
Starter code · place.mjs
/** The start position of each item on a flex line `lineSize` px long. */
export function place(lineSize, items, justify = 'flex-start') {
// Replace this line with your code.
return items.map(() => 0);
} The sample tests · place.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { place } from './place.mjs';
const near = (actual, expected) => {
assert.equal(actual.length, expected.length, `expected ${expected.length} positions, got ${actual.length}`);
actual.forEach((x, i) => assert.ok(Math.abs(x - expected[i]) < 0.01, `item ${i + 1}: expected ${expected[i]}, got ${x}`));
};
const three = [{ size: 80 }, { size: 80 }, { size: 80 }];
test('justify-content shares the free space', () => {
near(place(400, three), [0, 80, 160]);
near(place(400, three, 'center'), [80, 160, 240]);
near(place(400, three, 'flex-end'), [160, 240, 320]);
near(place(400, three, 'space-between'), [0, 160, 320]);
near(place(400, three, 'space-around'), [26.67, 160, 293.33]);
near(place(400, three, 'space-evenly'), [40, 160, 280]);
});
test('an auto margin takes all the free space first', () => {
const pushed = [{ size: 80 }, { size: 80 }, { size: 80, autoStart: true }];
near(place(400, pushed), [0, 80, 320]);
near(place(400, pushed, 'center'), [0, 80, 320]);
});
test('auto margins share the free space equally', () => {
near(place(400, [{ size: 80 }, { size: 80, autoStart: true, autoEnd: true }, { size: 80 }]), [0, 160, 320]);
near(place(400, [{ size: 80, autoEnd: true }, { size: 80 }, { size: 80, autoStart: true }]), [0, 160, 320]);
near(place(400, [{ size: 80, autoStart: true, autoEnd: true }]), [160]);
});
test('an overflowing item: center hangs over the start, the safe forms do not', () => {
const wide = [{ size: 300 }];
near(place(200, wide, 'center'), [-50]);
near(place(200, wide, 'safe center'), [0]);
near(place(200, [{ size: 300, autoStart: true, autoEnd: true }]), [0]);
near(place(200, wide, 'space-around'), [0]);
});
test('one item: space-between starts it, space-around centres it', () => {
near(place(400, [{ size: 80 }], 'space-between'), [0]);
near(place(400, [{ size: 80 }], 'space-around'), [160]);
}); A hint
Count the auto margins first. If there are any and the free space is positive, walk the items with a running x: add a share before an item that has autoStart, record x, add the item's size, then add a share after it if it has autoEnd. Otherwise work out two numbers from justify, the space before the first item and the extra space between items, and walk the items the same way.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Fresher · What is the difference between align-items and align-content?
Both work on the cross axis, at different levels. align-items places each item inside its own flex line: at the
start, in the centre, at the end, on the text baseline, or stretched to the line’s height, which is the default; an
item can override it with align-self. align-content moves and sizes the lines themselves, so it only matters when
a container can wrap: on a nowrap container it does nothing, because the single line always fills the container. In
a 200 px tall wrapping container with two lines of 24 px items, align-content: center packed both lines in the
middle, 76 px from the top, while the default, normal, stretched each line to 100 px and the items with it.
Key takeaways
align-selfoverridesalign-itemsfor one item;align-contentmoves whole lines and does nothing unless the container hasflex-wrap: wrap.align-items: baselineputs text of different sizes on one baseline; centring left a 16 px link 6 px off a 32 px heading’s baseline.- Auto margins take free space before
justify-content, somargin-inline-start: autopushes an item to the end andmargin-block-start: autopins a card’s button to its bottom. - Plain
centercan hide the start of an overflowing item;safe centeror auto margins keep it reachable. orderand-reversechange only the drawing: Tab order stayed in HTML order in all three engines. Reorder the HTML instead;reading-flowworks only in Chromium-based browsers.
References
- CSS Flexible Box Layout Module Level 1: aligning with auto margins (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: the align-items and align-self properties (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: the align-content property (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: reordering and accessibility (W3C CSS Working Group)
- CSS Box Alignment Module Level 3: overflow alignment (safe and unsafe) (W3C CSS Working Group)
- CSS Display Module Level 3: reordering and accessibility (W3C CSS Working Group)
- CSS Display Module Level 4: the reading-flow property (W3C CSS Working Group)
- WCAG 2.2: Success Criterion 1.3.2 Meaningful Sequence (W3C Web Accessibility Initiative)
- WCAG 2.2: Success Criterion 2.4.3 Focus Order (W3C Web Accessibility Initiative)
- Technique C27: Making the DOM order match the visual order (W3C Web Accessibility Initiative)
- Web features explorer: reading-flow (W3C WebDX Community Group)
- web-features 3.40.1 data: Flexbox, safe and last baseline alignment (W3C WebDX Community Group)
- Keyboard shortcuts and gestures in Safari on Mac (Apple)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress