CSS Module 10 – Flexbox
Flexbox fundamentals: axes, wrapping and gap
Make a flex container, read its main and cross axes, wrap items onto new lines, space them with gap, and place them with justify-content and align-items.
What you will learn
- Create a flex container and describe its main and cross axes
- Set the direction, wrapping and gaps of a flex container
- Arrange items along both axes with justify-content and align-items
Before you start
What you’ll learn · Before you start
What you will learn
- Create a flex container and describe its main and cross axes
- Set the direction, wrapping and gaps of a flex container
- Arrange items along both axes with justify-content and align-items
Before you start
On this page
display: flex turns an element into a flex container and its children into flex items, which it lays out
along one line, a row by default or a column. The direction of that line is the main axis, and the direction
across it is the cross axis. A few properties do the rest: flex-direction chooses the main axis, flex-wrap
lets items move onto new lines when they run out of room, and gap puts space between them. Then justify-content
places the items along the main axis and align-items places them along the cross axis. Flexbox is one-dimensional:
even when items wrap, each line is laid out on its own, so items on different lines do not line up in columns. That
is grid’s job. The tables give the positions and sizes that Chromium 153, Firefox 155 and WebKit 26.6 drew for each
example page.
Making a flex container
Two display values make one. display: flex makes a block-level flex container, which takes a line of its own like a
div, and display: inline-flex makes an inline-level one, which sits in a line of text like a button; inside, both
lay out their children the same way. Every child element becomes a flex item, and so does each run of loose text
between them:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A first flex container</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.row { display: flex; }
.row > * { margin: 0; background: #e6edfc; }
.row p { margin-block: 16px; } /* margins that would collapse in normal flow */
.stack { display: flex; flex-direction: column; }
.stack p, .flow p { margin-block: 16px; background: #eee; }
</style>
</head>
<body>
<div class="row">
<span>A span</span>
<a href="#top">A link</a>
Loose text
<p>A paragraph</p>
</div>
<div class="flow"><p>Block one</p><p>Block two</p></div>
<div class="stack"><p>Flex one</p><p>Flex two</p></div>
<script>
for (const el of document.querySelectorAll('.row > *')) {
const r = el.getBoundingClientRect();
console.log(`${el.localName}: display ${getComputedStyle(el).display}, x ${r.x}, y ${r.y}, ${r.width} x ${r.height}`);
}
const text = document.createRange();
text.selectNode(document.querySelector('.row').childNodes[4]);
const t = text.getBoundingClientRect();
console.log(`loose text: x ${t.x}, ${t.width} wide (an anonymous flex item)`);
for (const sel of ['.flow', '.stack']) {
const [a, b] = document.querySelectorAll(`${sel} p`);
console.log(`${sel}: ${b.getBoundingClientRect().top - a.getBoundingClientRect().bottom}px between the paragraphs`);
}
</script>
</body>
</html> All three engines put the four items side by side on one row. The span, the link and the paragraph all reported
display: block, because flex items are blockified: an inline element becomes a block box as soon as it is a
flex item, which the normal flow lesson explains for every display type. The text “Loose text” became an anonymous
flex item between the link and the paragraph, 73.83 px wide in Chromium.
Two more details came out of the same page. The span and the link were 56 px tall, as tall as the paragraph’s 24 px line plus its two 16 px margins, because flex items stretch across their line by default; the alignment section below explains why. And margins between flex items never collapse. The two paragraphs in the column container were 32 px apart, their 16 px margins added together, while the same paragraphs in normal flow were 16 px apart, because there the two margins collapsed into one.
A few things simply do not apply to flex items. float and clear do not float them, vertical-align does nothing,
and a child with position: absolute is taken out of the flex layout altogether.
The main axis and the cross axis
flex-direction sets the main axis: row (the default), row-reverse, column or column-reverse. A row follows
the direction of the text, not the screen, so in a right-to-left language it runs from the right, and in vertical
text it runs downwards. Six containers of 200 by 200 px, each holding three 40 px squares; the last two are a row in
right-to-left text (dir="rtl") and a row in vertical text (writing-mode: vertical-rl):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Where the main axis runs</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.box { display: flex; inline-size: 200px; block-size: 200px; margin-block-end: 8px; background: #eee; }
.box > span { inline-size: 40px; block-size: 40px; background: #e6edfc; }
#row { flex-direction: row; }
#row-reverse { flex-direction: row-reverse; }
#column { flex-direction: column; }
#column-reverse { flex-direction: column-reverse; }
#row-rtl { flex-direction: row; direction: rtl; }
#row-vertical { flex-direction: row; writing-mode: vertical-rl; }
</style>
</head>
<body>
<div class="box" id="row"><span>1</span><span>2</span><span>3</span></div>
<div class="box" id="row-reverse"><span>1</span><span>2</span><span>3</span></div>
<div class="box" id="column"><span>1</span><span>2</span><span>3</span></div>
<div class="box" id="column-reverse"><span>1</span><span>2</span><span>3</span></div>
<div class="box" id="row-rtl"><span>1</span><span>2</span><span>3</span></div>
<div class="box" id="row-vertical"><span>1</span><span>2</span><span>3</span></div>
<script>
// Each item's position inside its 200 x 200 box: x from the left edge, y from the top edge.
for (const box of document.querySelectorAll('.box')) {
const b = box.getBoundingClientRect();
const at = [...box.children].map((s) => {
const r = s.getBoundingClientRect();
return `${s.textContent} at (${r.x - b.x}, ${r.y - b.y})`;
});
console.log(`${box.id}: ${at.join(', ')}`);
}
</script>
</body>
</html> | Container | Squares 1, 2, 3 at | Main axis |
|---|---|---|
row |
0,0 · 40,0 · 80,0 | left to right |
row-reverse |
160,0 · 120,0 · 80,0 | right to left |
column |
0,0 · 0,40 · 0,80 | top to bottom |
column-reverse |
0,160 · 0,120 · 0,80 | bottom to top |
row, rtl |
160,0 · 120,0 · 80,0 | right to left |
row, vertical-rl |
160,0 · 160,40 · 160,80 | top to bottom |
The positions are x and y inside each container, and all three engines gave the same ones. Notice that row in
right-to-left text and row-reverse in left-to-right text look identical. That is why flexbox talks about the main
and cross axes, and about their start and end, instead of left and right: the same stylesheet then works in every
language.
The two axes of a flex container
Text description of the diagram
The diagram shows the same three flex items in two flex containers.
- With flex-direction: row, the items sit side by side. The main axis runs along the row, from left to right in English text, and justify-content places the items along it. The cross axis runs from top to bottom, and align-items places the items along that.
- With flex-direction: column, the items are stacked. Now the main axis runs from top to bottom, so justify-content works vertically, and the cross axis runs from left to right, so align-items works horizontally.
The -reverse values change only where items are drawn. Keyboard focus and screen readers still follow the order
of the HTML, so reversing a row of links makes the Tab key run backwards across the screen. The alignment lesson in
this module measures that, and shows when it matters.
Wrapping onto new lines
By default a flex container is single-line (flex-wrap: nowrap): every item stays on one line, however narrow
the container. flex-wrap: wrap makes it multi-line, so items that do not fit move to a new line, and
wrap-reverse stacks the new lines upwards instead of downwards. The shorthand flex-flow sets the direction and
the wrapping together, as in flex-flow: row wrap.
Here is a toolbar of six buttons that should wrap on a phone and stay vertically centred:
/* A toolbar that wraps on narrow screens: 0.75rem (12px) between the buttons and between the lines, every button
centred on its line. */
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
}
.toolbar button {
box-sizing: border-box;
block-size: 2rem;
}
.toolbar .primary {
block-size: 2.5rem;
} The example page gives the buttons fixed widths, 544 px in total, so that every engine lays out the same
boxes, and adds a second toolbar with flex-wrap: nowrap for comparison:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A toolbar that wraps</title>
<link rel="stylesheet" href="toolbar.css">
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
/* Fixed widths, so every engine lays out the same boxes: 88 + 72 + 104 + 96 + 64 + 120 = 544px of buttons. */
.toolbar button:nth-child(1) { inline-size: 88px; }
.toolbar button:nth-child(2) { inline-size: 72px; }
.toolbar button:nth-child(3) { inline-size: 104px; }
.toolbar button:nth-child(4) { inline-size: 96px; }
.toolbar button:nth-child(5) { inline-size: 64px; }
.toolbar button:nth-child(6) { inline-size: 120px; }
.nowrap { flex-wrap: nowrap; }
</style>
</head>
<body>
<div class="toolbar" id="wrap">
<button type="button">Bold</button><button type="button">Italic</button><button type="button">Underline</button>
<button type="button">Link</button><button type="button">Undo</button><button type="button" class="primary">Publish</button>
</div>
<div class="toolbar nowrap" id="nowrap">
<button type="button">Bold</button><button type="button">Italic</button><button type="button">Underline</button>
<button type="button">Link</button><button type="button">Undo</button><button type="button" class="primary">Publish</button>
</div>
<script>
for (const bar of document.querySelectorAll('.toolbar')) {
// Items whose vertical centres are equal sit on the same line.
const lines = new Map();
for (const b of bar.children) {
const r = b.getBoundingClientRect();
const centre = r.y + r.height / 2;
const key = [...lines.keys()].find((c) => Math.abs(c - centre) < 1) ?? centre;
lines.set(key, [...(lines.get(key) ?? []), { name: b.textContent, x: r.x, right: r.right, w: r.width, y: r.y - bar.getBoundingClientRect().y }]);
}
const desc = [...lines.values()].map((items, i) => {
const gaps = items.slice(1).map((it, j) => +(it.x - items[j].right).toFixed(2));
return `line ${i + 1}: ${items.map((it) => `${it.name} ${+it.w.toFixed(2)}px`).join(', ')}${gaps.length ? ` (gaps ${gaps.join(', ')})` : ''}, tops ${items.map((it) => it.y).join('/')}`;
});
console.log(`${bar.id} at ${innerWidth}px: ${lines.size} line(s); ${desc.join('; ')}; overflow ${bar.scrollWidth - bar.clientWidth}px`);
}
</script>
</body>
</html> | Screen | wrap |
nowrap |
|---|---|---|
| 320 px | lines of 3 and 3 | sticks out |
| 600 px | lines of 5 and 1 | squeezed |
| 1024 px | one line | one line |
At 600 px the buttons and their gaps need 544 + 5 × 12 = 604 px, four more than the toolbar has. With wrap,
Publish moved to the second line. With nowrap, flex items may shrink, so the browser took the 4 px from all six
buttons in proportion to their widths, 0.42 to 0.92 px each; the next lesson works out exactly how. At 320 px the
wrap toolbar put Bold, Italic and Underline on one line and Link, Undo and Publish on the next. The nowrap buttons
could shrink only to the width of their labels, and then they stuck out of the toolbar: by 47 px in Chromium, 33 px
in Firefox and 23 px in WebKit, because each engine draws buttons with its own padding and font. That is a good
reason never to rely on nowrap in a container that can get narrow.
Wrapped lines are independent. On the 600 px toolbar, the second line holds only Publish, and nothing on it lines up with the buttons above. When items must line up in rows and columns at the same time, use grid instead.
Spacing with gap
gap puts space between items and between lines, and never at the outer edges. It is shorthand for row-gap, the
space between lines, and column-gap, the space between items on a line, so gap: 0.75rem 1rem sets both. In the
toolbar every pair of neighbours on a line was exactly 12 px apart in all three engines, and the second line started
12 px below the first: the first line is 32 px tall, so the second begins at 44 px.
The gap also counts when lines break. An item stays on a line only if it fits there together with the gap in front of it, which is why the arithmetic above adds 5 × 12 px.
Before flexbox had gap, the spacing was done with margins on the items. That needed fixes of its own: a margin on
every item also adds space at the ends of each line, so a common trick was a negative margin on the container to
cancel it. gap for flexbox is Baseline widely available, supported since Chrome 84, Firefox 63 and Safari 14.1, so
those tricks are no longer needed.
Placing items: justify-content and align-items
Once items have their sizes, whatever space is left on a line is the free space. justify-content decides where
the free space goes along the main axis. Three 80 px items in a 400 px row leave 160 px:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>justify-content and align-items</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 { inline-size: 80px; background: #e6edfc; }
.box > div:nth-child(1) { block-size: 40px; }
.box > div:nth-child(2) { block-size: 60px; font-size: 32px; line-height: 40px; }
/* The third item has no height of its own, so stretch can size it. */
/* Main axis (a row: horizontal) */
#center { justify-content: center; }
#flex-end { justify-content: flex-end; }
#space-between, #gap-between, #one-between { justify-content: space-between; }
#space-around, #one-around { justify-content: space-around; }
#space-evenly, #gap-evenly { justify-content: space-evenly; }
#gap-between, #gap-evenly { gap: 20px; }
/* Cross axis (a row: vertical) */
#a-start { align-items: flex-start; }
#a-center { align-items: center; }
#a-end { align-items: flex-end; }
#a-baseline { align-items: baseline; }
/* A column swaps the axes: justify-content is now vertical, align-items horizontal. */
#column { flex-direction: column; block-size: 300px; justify-content: center; align-items: center; }
</style>
</head>
<body>
<div class="box" id="flex-start"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="center"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="flex-end"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="space-between"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="space-around"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="space-evenly"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="gap-between"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="gap-evenly"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="one-between"><div>1</div></div>
<div class="box" id="one-around"><div>1</div></div>
<div class="box" id="a-stretch"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="a-start"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="a-center"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="a-end"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="a-baseline"><div>1</div><div>2</div><div>3</div></div>
<div class="box" id="column"><div>1</div><div>2</div><div>3</div></div>
<script>
// Each item's position and size inside its box.
for (const box of document.querySelectorAll('.box')) {
const b = box.getBoundingClientRect();
const items = [...box.children].map((d) => {
const r = d.getBoundingClientRect();
return `${d.textContent}: x ${+(r.x - b.x).toFixed(2)}, y ${+(r.y - b.y).toFixed(2)}, ${r.width} x ${r.height}`;
});
console.log(`${box.id} | ${items.join(' | ')}`);
}
</script>
</body>
</html> justify-content |
Items start at | Free space |
|---|---|---|
flex-start |
0, 80, 160 | all at the end |
center |
80, 160, 240 | half at each end |
flex-end |
160, 240, 320 | all at the start |
space-between |
0, 160, 320 | 80 px per gap |
space-around |
26.67, 160, 293.33 | 53.33 px per item |
space-evenly |
40, 160, 280 | 40 px per space |
The default, normal, acts as flex-start. space-around gives each item an equal share and puts half of it on
each side, so the ends get half as much space as the gaps; space-evenly makes all five spaces equal. Firefox’s
positions matched these to the hundredth of a pixel. Chromium and WebKit store layout positions as whole
multiples of 1/64 px, so for space-around they gave 26.66, 159.98 and 293.33 (WebKit: 293.31). With gap: 20px
the free space shrinks to 120 px: space-between still gave 0, 160 and 320, and space-evenly gave 30, 160 and
290. With a single item,
space-between puts it at the start and space-around puts it in the centre. The rules fit in a few lines of code,
which print the same numbers the browsers drew:
// Where justify-content puts flex items: the free space of the line (its size minus the items' sizes and the gaps)
// goes before the first item and between the items, as CSS Box Alignment Level 3 defines each keyword.
// The numbers match what Chromium 153, Firefox 155 and WebKit 26.6 drew for justify-align.html.
function positions(lineSize, itemSizes, keyword, gap = 0) {
const n = itemSizes.length;
const free = lineSize - itemSizes.reduce((a, b) => a + b, 0) - gap * (n - 1);
let before = 0; // space before the first item
let between = 0; // extra space between two items, on top of the gap
if (keyword === 'center') before = free / 2;
if (keyword === 'flex-end') before = free;
if (keyword === 'space-between' && n > 1) between = free / (n - 1);
if (keyword === 'space-around') {
if (n > 1) [before, between] = [free / n / 2, free / n];
else before = free / 2; // one item: like center
}
if (keyword === 'space-evenly') [before, between] = [free / (n + 1), free / (n + 1)];
const xs = [];
let x = before;
for (const size of itemSizes) {
xs.push(Math.round(x * 100) / 100);
x += size + gap + between;
}
return { free, xs };
}
const keywords = ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'];
const three = [80, 80, 80];
console.log(`Three 80px items in a 400px line: ${positions(400, three, 'flex-start').free}px of free space`);
for (const k of keywords) console.log(` ${k.padEnd(14)} x = ${positions(400, three, k).xs.join(', ')}`);
console.log('The same items with gap: 20px (40px of the free space goes to the gaps)');
for (const k of ['space-between', 'space-evenly']) console.log(` ${k.padEnd(14)} x = ${positions(400, three, k, 20).xs.join(', ')}`);
console.log('One 80px item in the same line');
for (const k of ['space-between', 'space-around']) console.log(` ${k.padEnd(14)} x = ${positions(400, [80], k).xs.join(', ')}`); Output
Three 80px items in a 400px line: 160px of free space flex-start x = 0, 80, 160 center x = 80, 160, 240 flex-end x = 160, 240, 320 space-between x = 0, 160, 320 space-around x = 26.67, 160, 293.33 space-evenly x = 40, 160, 280 The same items with gap: 20px (40px of the free space goes to the gaps) space-between x = 0, 160, 320 space-evenly x = 30, 160, 290 One 80px item in the same line space-between x = 0 space-around x = 160
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node justify.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
There are two sets of start and end words. flex-start and flex-end follow the flex direction, so with
row-reverse the items of justify-content: flex-start were packed at the right. start and end follow the
writing direction instead, so with the same row-reverse the items of justify-content: start were packed at the
left, in all three engines.
align-items places each item along the cross axis, inside its line. The same container is 120 px tall; item 1 is
40 px tall, item 2 is 60 px tall with 32 px text, and item 3 has no height of its own:
align-items |
Tops of 1, 2, 3 | Item 3 is |
|---|---|---|
stretch |
0, 0, 0 | 120 px tall |
flex-start |
0, 0, 0 | 24 px tall |
center |
40, 30, 48 | 24 px tall |
flex-end |
80, 60, 96 | 24 px tall |
baseline |
14, 0, 14 | 24 px tall |
stretch, which is what the default normal does, only stretches items that have no cross size of their own, which
is why items 1 and 2 kept their heights.
baseline moves the items so that the first lines of their text sit on one baseline. Firefox put items 1 and 3 at
13.5 px, Chromium at 14 px and WebKit at 13.55 px: half a pixel at most between engines, which round the same font’s
measurements differently.
Put both properties together and you can centre anything: display: flex; justify-content: center; align-items: center. In a column the two properties swap directions. In a 400 by 300 px column with both set to center, the
three items stacked in the middle: their tops were at 88, 128 and 188 px, which leaves 88 px above and below their
124 px, and all three were 160 px from the left edge, centred horizontally.
A navigation bar with an auto margin
A logo at one end and links at the other is one of the most common flex layouts. Give the links an auto margin on the side that faces the logo, and that margin takes all the free space of the line:
/* Logo at the start, links at the end: the auto margin takes all the free space between them. Logical margins,
so in right-to-left text the links move to the left edge without another rule. */
.site-nav {
display: flex;
align-items: center;
gap: 1rem;
}
.site-nav .links {
display: flex;
gap: 1rem;
margin-inline-start: auto;
padding: 0;
list-style: none;
} <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A navigation bar</title>
<link rel="stylesheet" href="navbar.css">
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.site-nav { margin-block-end: 8px; }
.site-nav .links { margin-block: 0; }
</style>
</head>
<body>
<nav class="site-nav" aria-label="Main">
<a href="#home" class="logo">Lotus Books</a>
<ul class="links"><li><a href="#shop">Shop</a></li><li><a href="#blog">Blog</a></li><li><a href="#help">Help</a></li></ul>
</nav>
<nav class="site-nav" aria-label="Main, in Urdu" dir="rtl" lang="ur">
<a href="#home" class="logo">لوٹس بکس</a>
<ul class="links"><li><a href="#shop">دکان</a></li><li><a href="#blog">بلاگ</a></li><li><a href="#help">مدد</a></li></ul>
</nav>
<script>
for (const nav of document.querySelectorAll('.site-nav')) {
const n = nav.getBoundingClientRect();
const logo = nav.querySelector('.logo').getBoundingClientRect();
const links = nav.querySelector('.links').getBoundingClientRect();
const margin = getComputedStyle(nav.querySelector('.links'));
console.log(`${nav.dir || 'ltr'} at ${innerWidth}px: logo ${+(logo.left - n.left).toFixed(2)} to ${+(logo.right - n.left).toFixed(2)}, links ${+(links.left - n.left).toFixed(2)} to ${+(links.right - n.left).toFixed(2)} of ${n.width}; auto margin used ${nav.dir === 'rtl' ? margin.marginRight : margin.marginLeft}`);
}
</script>
</body>
</html> At 1024 px the links ended exactly at the right edge in all three engines, and their auto margin was 785.63 px wide
in Chromium and WebKit (785.65 px in Firefox), the whole free space of the line. In the Urdu copy, which has
dir="rtl", the logo moved to the right edge and the links to the left, without another rule, because
margin-inline-start is the margin at the start of the line in whatever direction the text runs. A physical
margin-left: auto would have pushed the Urdu links the wrong way. Auto margins can do much more than this; the
alignment lesson in this module covers them in full.
Practise: predict a wrapping toolbar
Exercise · Easy · JavaScript
Predict where a wrapping toolbar breaks into lines
The lesson's toolbar is a flex container with flex-wrap: wrap, gap: 0.75rem (12 px) and align-items: center. Its six buttons are 88, 72, 104, 96, 64 and 120 px wide; the first five are 32 px tall and the last, "Publish", is 40 px tall. Write two functions in toolbar.mjs and export them. All lengths are in px.
The first, flexLines(widths, containerWidth, gap), returns the flex lines as an array of lines, each line an array of item indexes (0-based). Items go onto the current line one by one, with gap between neighbours, until the next one would make the line longer than containerWidth; that item starts a new line. The first item of a line always goes on it, even when it is wider than the container on its own.
The second, toolbar(containerWidth), returns { lines, tops } for the toolbar: how many lines it has and the top edge of each button, measured from the top of the toolbar. A line is as tall as its tallest button, the lines are 12 px apart, and align-items: center puts each button in the middle of its line.
The sample tests import both functions from toolbar.mjs and run in your browser. Their numbers are the ones Chromium 153, Firefox 155 and WebKit 26.6 drew for the lesson's toolbar at 320, 600 and 1024 px.
Starter code · toolbar.mjs
/** The flex lines of a wrapping container: an array of lines, each an array of item indexes. */
export function flexLines(widths, containerWidth, gap) {
// Replace this line with your code.
return [widths.map((_, i) => i)];
}
/** The lesson's toolbar in a container `containerWidth` px wide: the number of lines and each button's top edge. */
export function toolbar(containerWidth) {
// Replace this line with your code.
return { lines: 1, tops: [0, 0, 0, 0, 0, 0] };
} The sample tests · toolbar.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { flexLines, toolbar } from './toolbar.mjs';
const widths = [88, 72, 104, 96, 64, 120];
test('the toolbar at 320, 600 and 1024 px', () => {
assert.deepEqual(flexLines(widths, 320, 12), [[0, 1, 2], [3, 4, 5]]);
assert.deepEqual(flexLines(widths, 600, 12), [[0, 1, 2, 3, 4], [5]]);
assert.deepEqual(flexLines(widths, 1024, 12), [[0, 1, 2, 3, 4, 5]]);
});
test('an item that fits exactly stays on the line', () => {
assert.deepEqual(flexLines([100, 100], 212, 12), [[0, 1]]);
assert.deepEqual(flexLines([100, 100], 211, 12), [[0], [1]]);
});
test('the gap counts only between items', () => {
assert.deepEqual(flexLines([100, 100, 100], 300, 0), [[0, 1, 2]]);
assert.deepEqual(flexLines([300], 300, 50), [[0]]);
});
test('an item wider than the container gets a line of its own', () => {
assert.deepEqual(flexLines([50, 400, 50], 300, 10), [[0], [1], [2]]);
});
test('every button is centred on its line', () => {
assert.deepEqual(toolbar(320), { lines: 2, tops: [0, 0, 0, 48, 48, 44] });
assert.deepEqual(toolbar(600), { lines: 2, tops: [0, 0, 0, 0, 0, 44] });
assert.deepEqual(toolbar(1024), { lines: 1, tops: [4, 4, 4, 4, 4, 0] });
});
test('a very narrow toolbar puts every button on its own line', () => {
assert.deepEqual(toolbar(150), { lines: 6, tops: [0, 44, 88, 132, 176, 220] });
}); A hint
Keep the current line and how long it is so far. An item fits when length + gap + width is at most the container width; on an empty line it always fits. For toolbar, walk the lines with a running top: each button's top is the line's top plus half of (line height − button height), and the next line starts line height + 12 px lower.
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 justify-content and align-items?
Both place flex items, along different axes. justify-content works along the main axis, the direction the items
flow in: it decides where the free space of each line goes, so it can pack the items at the start, the centre or the
end, or spread them out with space-between, space-around and space-evenly. align-items works along the cross
axis: it places each item inside its own line, at the start, centre or end, on the text baseline, or stretched to the
line’s full size, which is the default. In a row, justify-content is horizontal and align-items vertical. In a
column they swap, which is why justify-content: center centres a column vertically. To centre one item in both
directions, set both properties to center.
Key takeaways
display: flexlays its children out along one line;flex-directionpicks the main axis, and the cross axis runs across it. A row follows the text direction, so it runs right to left in Urdu or Arabic.- Flex items are blockified, their margins never collapse, and loose text becomes an anonymous item.
flex-wrap: wrapmoves items that do not fit onto new lines. Each line is laid out on its own, so wrapped items do not form columns.gapspaces items and lines, never the outer edges, and counts when lines break.justify-contentshares out the free space along the main axis;align-itemsplaces items inside their line, stretching those without a cross size by default.- Use a logical auto margin,
margin-inline-start: auto, to push items to the end of a line in any language.
References
- CSS Flexible Box Layout Module Level 1: flex containers (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: flex items (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: flex item margins and paddings (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: the flex-direction property (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: the flex-wrap property (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: the justify-content property (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: the align-items and align-self properties (W3C CSS Working Group)
- CSS Box Alignment Module Level 3: gaps between boxes (W3C CSS Working Group)
- CSS Box Alignment Module Level 3: distributed alignment (W3C CSS Working Group)
- CSS Display Module Level 3: automatic box type transformations (W3C CSS Working Group)
- CSS Snapshot 2026: the official definition of CSS (W3C CSS Working Group)
- Web features explorer: Flexbox gap (W3C WebDX Community Group)
- LayoutUnit (WebKit wiki) (WebKit)
- Blink source: layout_unit.h (The Chromium Project)
- Basic concepts of flexbox (MDN Web Docs) (Mozilla)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress