Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 25 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free: 25 minutes

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:

A span, a link, loose text and a paragraph in a flex row HTML · first-flex.html
<!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):

Three squares in six directions HTML · axes.html
<!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.

In a row the main axis runs left to right and the cross axis top to bottom; a column swaps them.Main axis and cross axisflex-direction: row123main axisjustify-contentcrossaxisalign-itemsflex-direction: column123mainaxisjustify-contentcross axisalign-items

The two axes of a flex container

Text description of the diagram

The diagram shows the same three flex items in two flex containers.

  1. 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.
  2. 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 CSS · toolbar.css
/* 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:

The toolbar at three widths HTML · toolbar.html
<!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:

Three items, every justify-content and align-items value HTML · justify-align.html
<!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 the items JavaScript · justify.mjs
// 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

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 CSS · navbar.css
/* 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;
}
The bar in English and in Urdu HTML · navbar.html
<!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.

CSS Flexbox Generator & Playground Change the direction, wrapping, gap and alignment of live flex items and watch every value from this lesson move them.

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.

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.

  1. Question 1 of 6 A flex container has flex-direction: column and a fixed height. Which property centres its items vertically?

    Choose one answer.

    Show the answer to question 1

    Answer: justify-content: center

    In a column the main axis runs from top to bottom, and justify-content works along the main axis. align-items works along the cross axis, which in a column is horizontal, and vertical-align has no effect on flex items.

  2. Question 2 of 6 Four 50px items sit in a 400px flex row with justify-content: space-evenly and no gap. Where does the first item start?

    Type a number.

    Show the answer to question 2

    Answer: 40 px

    The free space is 400 − 4 × 50 = 200px. space-evenly makes the five spaces equal, before the first item, between the items and after the last: 200 ÷ 5 = 40px, so the first item starts at 40px.

  3. Question 3 of 6 A <span> is a child of an element with display: flex. What does getComputedStyle(span).display return?

    Choose one answer.

    Show the answer to question 3

    Answer: block

    Flex items are blockified: an inline display type becomes its block equivalent, so the span computes to block. All three engines reported block for the lesson's span and link.

  4. Question 4 of 6 Six 100px buttons sit in a toolbar with flex-wrap: wrap and gap: 12px. The toolbar is 500px wide. How many buttons are on its first line?

    Type a number.

    Show the answer to question 4

    Answer: 4

    Four buttons need 4 × 100 + 3 × 12 = 436px, which fits in 500px. A fifth would need 436 + 12 + 100 = 548px, so it starts the second line. The gap counts when lines break.

  5. Question 5 of 6 Two paragraphs, each with margin-block: 16px, are stacked in a flex container with flex-direction: column. How far apart are they?

    Choose one answer.

    Show the answer to question 5

    Answer: 32px

    Margins of flex items never collapse, so the bottom margin of the first paragraph and the top margin of the second add up to 32px. In normal flow they would collapse into one 16px margin, as the lesson measured.

  6. Question 6 of 6 A navigation bar has a logo and a list of links. Which declaration on the links moves them to the end of the line in both English and Urdu?

    Choose one answer.

    Show the answer to question 6

    Answer: margin-inline-start: auto

    margin-inline-start is the margin at the start of the line in the text's own direction, so its auto value takes the free space between the logo and the links in left-to-right and right-to-left text alike. margin-left: auto would push the Urdu links the wrong way, and float and vertical-align do not apply to flex items.

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: flex lays its children out along one line; flex-direction picks 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: wrap moves items that do not fit onto new lines. Each line is laid out on its own, so wrapped items do not form columns.
  • gap spaces items and lines, never the outer edges, and counts when lines break.
  • justify-content shares out the free space along the main axis; align-items places 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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.