CSS justify-content: how it places items and when it does nothing

justify-content aligns a flex line along its main axis, or grid tracks along the inline axis. The examples show where remaining space goes and why growth or auto margins can leave none to distribute.

justify-content aligns flex items along the main axis and grid tracks along the inline axis. With horizontal left-to-right writing, a flex row runs left to right. Put it on the container, not the items: display: flex; justify-content: center; centres a row of items.

Try all six values. The striped area is free space, the part of the line the items do not use.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>justify-content playground</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
  .controls b { width: 100%; font-size: 13px; }
  button { font: inherit; font-size: 13px; padding: 6px 10px; border: 1px solid #c9ced8; border-radius: 8px; background: #fff; cursor: pointer; }
  button.on { background: #1d4ed8; border-color: #1d4ed8; color: #fff; }

  /* the flex container: stripes show the free space justify-content hands out */
  .box {
    display: flex;
    justify-content: flex-start;
    height: 150px; margin-top: 12px; border: 2px solid #1d4ed8; border-radius: 10px;
    background: repeating-linear-gradient(45deg, #fff3d6 0 8px, #fde7b0 8px 16px);
  }
  .item {
    width: 64px; height: 40px; border-radius: 8px;
    background: #1d4ed8; color: #fff; font-weight: 700;
    display: grid; place-items: center;
  }
  .box.grow .item { flex-grow: 1; }  /* items eat the free space */
  code { display: block; margin-top: 10px; padding: 8px 10px; border-radius: 8px; background: #1d2330; color: #e5e7eb; font-size: 13px; white-space: pre; overflow-x: auto; }
</style>
</head>
<body>
<div class="controls" id="jc">
  <b>justify-content</b>
  <button class="on">flex-start</button><button>center</button><button>flex-end</button>
  <button>space-between</button><button>space-around</button><button>space-evenly</button>
</div>
<div class="controls" id="dir">
  <b>flex-direction</b>
  <button class="on">row</button><button>column</button>
  <button id="grow">flex-grow: 1 on items</button>
</div>

<div class="box" id="box">
  <div class="item">1</div><div class="item">2</div><div class="item">3</div>
</div>
<code id="css"></code>

<script>
  const box = document.getElementById('box');
  const css = document.getElementById('css');

  function show() {
    css.textContent = '.box {\n  display: flex;\n  flex-direction: ' + box.style.flexDirection +
      ';\n  justify-content: ' + box.style.justifyContent + ';\n}' +
      (box.classList.contains('grow') ? '\n.item { flex-grow: 1; }  /* no free space left */' : '');
  }

  // one group of buttons sets one property on the container
  function group(id, prop) {
    const btns = document.querySelectorAll('#' + id + ' button:not(#grow)');
    btns.forEach((b) => b.addEventListener('click', () => {
      btns.forEach((x) => x.classList.remove('on'));
      b.classList.add('on');
      box.style[prop] = b.textContent;
      show();
    }));
    box.style[prop] = btns[0].textContent;
  }
  group('jc', 'justifyContent');
  group('dir', 'flexDirection');

  document.getElementById('grow').addEventListener('click', (e) => {
    e.target.classList.toggle('on', box.classList.toggle('grow'));
    show();
  });
  show();
</script>
</body>
</html>
Three items in a flex container. Switch the value, the direction, and flex-grow, and watch the stripes.

In this demo, turn on flex-grow: 1: the items consume the available space, so the spacing controls no longer change their placement.

Six common flexbox values

justify-content distributes remaining space for the six flexbox settings below. These are not every supported value: the initial value is normal, and grid also supports track stretching.

The same three items and the same free space, placed by each value.
The same three items and the same free space, placed by each value.
Value Where the free space goes
flex-start Pack against the main-start edge. In flexbox, the initial normal value behaves this way.
center Half before, half after.
flex-end All before the items.
space-between Between the items only. The first and last touch the edges.
space-around Around each item, so the ends get half a share.
space-evenly Equal slices before, between and after.

Use start and end to describe grid alignment. Grid accepts flex-start and flex-end too, treating them as start and end. In flexbox, flex-start follows the main-start edge; start follows the relevant writing-mode start edge. Reversing the flex direction can make them differ.

It works along the main axis

In flexbox, the main axis follows flex-direction. With horizontal left-to-right writing, a normal row runs left to right. Change flex-direction to column and it runs top to bottom, so justify-content now moves items up and down.

justify-content follows the main axis. align-items works across it.
justify-content follows the main axis. align-items works across it.

That is why vertical centring with justify-content: center needs two things: flex-direction: column and a height on the container. A column with no height is exactly as tall as its items, so there is no free space to share.

.hero {
  display: flex;
  flex-direction: column;
  justify-content: center;  /* up and down, because of column */
  min-height: 60vh;         /* without a height there is nothing to share */
}

justify-content vs align-items

The two properties split the job. justify-content works along the main axis and moves the whole group of items. align-items works across it and places each item inside the line.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>justify-content vs align-items</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .top { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 13px; }
  button { font: inherit; padding: 6px 10px; border: 1px solid #c9ced8; border-radius: 8px; background: #fff; cursor: pointer; }
  button.on { background: #1d4ed8; border-color: #1d4ed8; color: #fff; }

  /* 3 x 3 table of small flex containers */
  .table { display: grid; grid-template-columns: 70px repeat(3, 1fr); gap: 6px; margin-top: 12px; font-size: 12px; }
  .head { font-weight: 700; align-self: end; text-align: center; overflow-wrap: anywhere; }
  .side { font-weight: 700; align-self: center; overflow-wrap: anywhere; }
  .cell {
    display: flex;              /* each cell is a flex container */
    height: 80px; border: 1px solid #c9ced8; border-radius: 8px; background: #fff; cursor: pointer;
  }
  .cell.pick { outline: 3px solid #f59e0b; }
  .dot { width: 22px; height: 22px; border-radius: 6px; background: #1d4ed8; }
  code { display: block; margin-top: 12px; padding: 8px 10px; border-radius: 8px; background: #1d2330; color: #e5e7eb; font-size: 13px; white-space: pre; overflow-x: auto; }
</style>
</head>
<body>
<div class="top">
  <span>flex-direction:</span>
  <button class="on" data-dir="row">row</button>
  <button data-dir="column">column</button>
</div>
<div class="table" id="table"></div>
<code id="css"></code>

<script>
  const values = ['flex-start', 'center', 'flex-end'];
  const table = document.getElementById('table');
  const css = document.getElementById('css');
  let dir = 'row', picked = null;

  // column headers = justify-content, row headers = align-items
  table.innerHTML = '<div class="side">align &darr;<br>justify &rarr;</div>' +
    values.map((v) => '<div class="head">' + v + '</div>').join('');
  values.forEach((ai) => {
    table.insertAdjacentHTML('beforeend', '<div class="side">' + ai + '</div>');
    values.forEach((jc) => {
      const cell = document.createElement('div');
      cell.className = 'cell';
      cell.style.justifyContent = jc;
      cell.style.alignItems = ai;
      cell.innerHTML = '<div class="dot"></div>';
      cell.addEventListener('click', () => { picked = cell; show(); });
      table.appendChild(cell);
    });
  });

  function show() {
    document.querySelectorAll('.cell').forEach((c) => {
      c.style.flexDirection = dir;
      c.classList.toggle('pick', c === picked);
    });
    const c = picked || document.querySelectorAll('.cell')[0];
    css.textContent = '.cell {\n  display: flex;\n  flex-direction: ' + dir +
      ';\n  justify-content: ' + c.style.justifyContent +
      ';  /* main axis */\n  align-items: ' + c.style.alignItems + ';  /* cross axis */\n}';
  }

  document.querySelectorAll('[data-dir]').forEach((b) => b.addEventListener('click', () => {
    document.querySelectorAll('[data-dir]').forEach((x) => x.classList.remove('on'));
    b.classList.add('on');
    dir = b.dataset.dir;
    show();
  }));
  show();
</script>
</body>
</html>
Columns set justify-content, rows set align-items. Click a cell for its CSS, and switch to column to see the axes swap.

Centring one thing in both directions uses one of each:

.box { display: flex; justify-content: center; align-items: center; }

In a row, the middle column of the table moves sideways and the middle row moves vertically. Switch to column and the same cells move the other way. The flexbox guide covers the rest of the flex properties.

It needs free space

For the spacing examples, remaining space is what is left after item sizing, gaps and auto margins. Distributed values need available space; oversized content can still overflow when centered. Use safe center when alignment should fall back to start rather than overflow the start edge.

Left: the items use the whole line. Right: space is left over, so center has an effect.
Left: the items use the whole line. Right: space is left over, so center has an effect.

Three things remove free space. flex-grow makes the items stretch into it. Items with a large flex-basis or width fill the line and shrink to fit. And a column container with no height has none to begin with.

When you want items to grow but still be centred, cap them with max-width. They grow up to the cap, and whatever is left over goes back to justify-content.

gap vs space-between

Both affect spacing, but they measure it differently. gap is a fixed distance you choose. The space-* values share out whatever is left, so the distance changes with the container width.

gap: 16px justify-content: space-between
Distance between items Always 16px Whatever is left over
Space at the edges None None
On a wider screen Items stay together Items drift apart

They combine well. With both set, gap becomes the minimum distance and space-between shares out the rest. Without gap, items with space-between can end up touching when the line is nearly full.

Push one item with margin-left: auto

Flex items have no justify-self, so no alignment property moves one item on its own. An auto margin does. margin-left: auto on an item takes all the free space to its left, pushing it and everything after it to the right.

.nav { display: flex; align-items: center; gap: 14px; }
.nav a:first-of-type { margin-left: auto; }  /* logo left, links right */

Auto margins take the free space before justify-content sees it. With one auto margin in the line, justify-content has nothing left to place. The finished example below uses this for a nav bar, and the navbar guide builds a full one.

The last row of a wrapped list

space-between works line by line. In a wrapped list, the last row often has fewer items, and space-between still spreads them to the two ends. Two tiles land far left and far right, out of line with the columns above.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>justify-content in a real page</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  h2 { font-size: 13px; margin: 18px 0 6px; color: #4b5563; }
  h2:first-child { margin-top: 0; }

  /* 1. Nav bar: logo left, links pushed right with an auto margin */
  .nav { display: flex; align-items: center; gap: 14px; padding: 10px 14px; border-radius: 10px; background: #1d2330; }
  .logo { color: #fff; font-weight: 800; }
  .nav a { color: #cbd5e1; text-decoration: none; font-size: 14px; }
  .nav a:first-of-type { margin-left: auto; }  /* takes all the free space */

  /* 2. Card footer: price and button at the two ends */
  .card { max-width: 320px; border-radius: 10px; background: #fff; box-shadow: 0 4px 14px rgba(0, 0, 0, .08); overflow: hidden; }
  .card p { margin: 0; padding: 14px; font-size: 14px; }
  .footer { display: flex; justify-content: space-between; align-items: center; padding: 10px 14px; border-top: 1px solid #e5e7eb; }
  .footer button { font: inherit; padding: 6px 12px; border: 0; border-radius: 8px; background: #1d4ed8; color: #fff; cursor: pointer; }

  /* 3. Wrapped list of tiles */
  .tiles { padding: 8px; border-radius: 10px; background: #fff; }
  .tiles span { padding: 8px 0; border-radius: 8px; background: #dbeafe; text-align: center; font-size: 13px; }
  .tiles.flex { display: flex; flex-wrap: wrap; justify-content: space-between; row-gap: 8px; }
  .tiles.flex span { width: 22%; }  /* 4 per row, 6 tiles: 2 end up on the last row */
  .tiles.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
  .tiles.gap { display: flex; flex-wrap: wrap; gap: 8px; }
  .tiles.gap span { width: calc((100% - 24px) / 4); }  /* 3 gaps of 8px per row */
  .tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
  .tabs button { font: inherit; font-size: 13px; padding: 6px 10px; border: 1px solid #c9ced8; border-radius: 8px; background: #fff; cursor: pointer; }
  .tabs button.on { background: #1d4ed8; border-color: #1d4ed8; color: #fff; }
  #note { font-size: 13px; margin: 8px 0 0; }
</style>
</head>
<body>
<h2>Nav bar: margin-left: auto on the first link</h2>
<nav class="nav">
  <span class="logo">Acme</span>
  <a href="#pricing">Pricing</a><a href="#docs">Docs</a><a href="#login">Log in</a>
</nav>

<h2>Card footer: justify-content: space-between</h2>
<div class="card">
  <p>Starter plan. One project, share links included.</p>
  <div class="footer"><b>$9 / month</b><button type="button">Choose</button></div>
</div>

<h2>Wrapped list: the last-row problem</h2>
<div class="tabs" id="tabs">
  <button class="on" data-mode="flex">flex + space-between</button>
  <button data-mode="grid">grid</button>
  <button data-mode="gap">flex + gap</button>
</div>
<div class="tiles flex" id="tiles">
  <span>HTML</span><span>CSS</span><span>Flexbox</span><span>Grid</span><span>Forms</span><span>Tables</span>
</div>
<p id="note"></p>

<script>
  const tiles = document.getElementById('tiles');
  const note = document.getElementById('note');
  const notes = {
    flex: 'The last row has 2 tiles, so space-between pushes them to the two ends.',
    grid: 'Grid places tiles in fixed columns, so the last row lines up.',
    gap: 'No space-between: a fixed gap keeps the last row packed to the left.'
  };

  document.querySelectorAll('#tabs button').forEach((b) => b.addEventListener('click', () => {
    document.querySelectorAll('#tabs button').forEach((x) => x.classList.remove('on'));
    b.classList.add('on');
    tiles.className = 'tiles ' + b.dataset.mode;
    note.textContent = notes[b.dataset.mode];
  }));
  note.textContent = notes.flex;
</script>
</body>
</html>
A nav bar with an auto margin, a card footer with space-between, and a wrapped list. Switch the list between flex + space-between, grid and flex + gap.

There are two fixes. Use CSS Grid, which puts every tile in a fixed column. Or drop space-between and use gap with a fixed tile width, so the last row packs to the left.

The CSS Grid guide and grid wrap guide show grids that also change their column count on phones.

justify-content in a grid

In a grid, justify-content aligns the tracks along the inline axis, not each item within its cell. With ordinary horizontal writing, fixed-width columns can leave space for this alignment. Flexible 1fr tracks consume the available space. The initial normal behavior can stretch auto-sized tracks.

To place items inside their own cells, use justify-items on the grid, or justify-self on one item. Both work in grid. The display guide explains when to pick grid over flex.

When it does not work

What you see Cause Fix
No value changes anything The items fill the line, so there is no free space Give items a width or max-width
Items stay stretched edge to edge flex-grow or flex: 1 on the items Remove it, or cap it with max-width
Nothing moves at all justify-content is on the items, not the container Move it to the parent with display: flex
It moves items up and down, not sideways flex-direction: column swaps the axes Use align-items for sideways
center does not centre vertically A column container with no height Add height or min-height
One item stays on the left with space-between There is only one item, so there is nothing to put space between Use center, or an auto margin
Last row tiles jump to the edges space-between on a wrapped list Use grid, or gap without space-between

References

Check MDN justify-content for supported values and CSS Box Alignment for layout-specific behavior.

Layout is easier to show than to describe. A screenshot shows one screen width, and the person you send it to cannot resize it or click through the values.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so others can resize the window and try each value themselves. If you change the CSS later, the same link shows the new version.

Questions people ask

What is the difference between justify-content and align-items?

In flexbox, justify-content aligns along the main axis and align-items across it. In grid, justify-content aligns the tracks along the inline axis; justify-items aligns items within their cells. Row and column examples here assume horizontal writing.

Why is justify-content: center not working?

Usually there is no free space to share. The items fill the line, or they have flex-grow, or a column container has no height. It also does nothing when it is set on the items instead of on the flex or grid container.

Should I use gap or space-between?

Use gap when you want a fixed distance between items. Use space-between when the first and last items must touch the two edges and the rest of the space is shared out. You can set both: gap then acts as the minimum distance.

Is there a justify-self for flex items?

No. justify-self is ignored on flex items. To move one flex item on its own along the main axis, give it an auto margin, such as margin-left: auto to push it to the right.

What is the difference between flex-start and start?

flex-start follows the flex direction, so with row-reverse it packs items to the right. start follows the writing direction, so in left-to-right text it packs them to the left even with row-reverse. With a normal row the two look the same.

Keep reading