CSS gap: space between items without margins

gap puts space between the children of a flex, grid or multi-column container, and never around the outside. One line on the parent replaces margins on every child.

The CSS gap property sets the space between the children of a flex, grid or multi-column container. It goes on the parent, not on the children, and it never adds space at the outer edges.

.row { display: flex; flex-wrap: wrap; gap: 16px; }

Try it below. Switch between margins on every card, the old negative margin fix, and gap. Watch the dashed box and the edge space in the note.

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>gap vs margin</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
  .controls button {
    font: inherit; font-size: 14px; padding: 7px 12px; border-radius: 8px;
    border: 1px solid #c9ced8; background: #fff; cursor: pointer;
  }
  .controls button[aria-pressed="true"] { background: #1d2330; color: #fff; border-color: #1d2330; }

  /* the container: a dashed outline so you can see its edges */
  .row {
    display: flex; flex-wrap: wrap;
    outline: 2px dashed #9aa3b2;
  }
  .card {
    flex: 0 0 110px; height: 70px; border-radius: 10px;
    background: #dbe7ff; display: grid; place-items: center; font-weight: 600;
  }

  /* 1. margins on every card: space leaks out at the edges */
  .row.margin .card { margin: 8px; }
  /* 2. the old fix: a negative margin on the row cancels the edge space */
  .row.hacks { margin: -8px; }
  .row.hacks .card { margin: 8px; }
  /* 3. gap: space only between cards, nothing at the edges */
  .row.gap { gap: 16px; }

  /* the parent: a solid line, to show when the row spills out of it */
  .wrap { border: 2px solid #f0a37a; border-radius: 4px; }
  #note { font-size: 14px; line-height: 1.45; margin: 12px 0 0; }
</style>
</head>
<body>
<div class="controls">
  <button data-mode="margin" aria-pressed="true">margin: 8px</button>
  <button data-mode="hacks" aria-pressed="false">margin + negative margin</button>
  <button data-mode="gap" aria-pressed="false">gap: 16px</button>
</div>

<div class="wrap">
  <div class="row margin" id="row">
    <div class="card">1</div><div class="card">2</div><div class="card">3</div>
    <div class="card">4</div><div class="card">5</div>
  </div>
</div>
<p id="note"></p>

<script>
  const row = document.getElementById('row');
  const note = document.getElementById('note');
  const text = {
    margin: 'Every card carries 8px on all sides, so 8px leaks out at every edge of the dashed box, and neighbours sit 16px apart.',
    hacks: 'A -8px margin on the dashed box cancels the edge space. The box is now 16px wider than its parent (orange line) and spills out on both sides.',
    gap: 'gap puts 16px between cards and between lines. The cards touch the edges on every side. No fixes needed.'
  };

  function measure() {
    // distance from the parent's inner top-left corner to the first card
    const r = row.parentElement.getBoundingClientRect();
    const c = row.firstElementChild.getBoundingClientRect();
    return Math.round(c.left - r.left - 2) + 'px left, ' + Math.round(c.top - r.top - 2) + 'px top';
  }

  document.querySelectorAll('.controls button').forEach((btn) => {
    btn.addEventListener('click', () => {
      document.querySelectorAll('.controls button').forEach((b) => b.setAttribute('aria-pressed', b === btn));
      row.className = 'row ' + btn.dataset.mode;
      note.textContent = text[btn.dataset.mode] + ' Edge space: ' + measure() + '.';
    });
  });
  note.textContent = text.margin + ' Edge space: ' + measure() + '.';
</script>
</body>
</html>
The same five cards spaced three ways. Only gap leaves the edges clean without a fix.

Where gap works: flexbox, grid and multi-column

gap is part of the box alignment rules shared by three layout modes. In any other layout it is accepted without error but has no effect.

gap spaces flex items, grid tracks and columns of text. On a normal block it does nothing.
gap spaces flex items, grid tracks and columns of text. On a normal block it does nothing.
Container What row-gap does What column-gap does Default
display: flex Space between wrapped lines Space between items on a line 0
display: grid Space between rows Space between columns 0
columns (multi-column) Not used in basic columns Space between columns of text 1em
display: block Nothing Nothing
display: table Nothing Nothing Use border-spacing

With flex-direction: column, the items are stacked, so row-gap is the space between them. That makes a column flexbox a handy way to space a stack of blocks.

gap vs margin

Margins belong to each item. A card with margin: 8px carries 8px on all four sides, so the first and last cards push 8px of space out past the container. Two neighbours end up 16px apart, twice the edge space.

Margins leak space at every edge. gap sits only between items, and the container edge stays tight.
Margins leak space at every edge. gap sits only between items, and the container edge stays tight.

The usual fixes each have a cost:

  • A negative margin on the container cancels the edge space, but the container becomes wider than its parent and can cause sideways scrolling.
  • A :last-child rule removes one margin, but in a wrapping row the last item on each line is not the last child, so those lines keep the extra space.
  • * + * selectors such as .item + .item { margin-left: 8px } only work for one line.

gap needs none of them. Space goes between items and between lines, and nothing goes outside. For space around the outside, use padding on the container. The box model explains how padding and margin sit around content.

Margins are still right for spacing inside text, such as paragraphs in an article, where the parent is a normal block.

row-gap, column-gap and the shorthand

gap is shorthand for two properties. One value sets both. Two values set row-gap first, then column-gap.

.list { gap: 12px 24px; }  /* 12px between lines, 24px between items */

Drag the sliders to change each direction on a wrapping flex list and on a three-column grid.

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>row-gap and column-gap</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  label { display: flex; align-items: center; gap: 10px; font-size: 14px; margin-bottom: 8px; }
  label input[type="range"] { flex: 1; max-width: 220px; }
  output { font: 600 13px ui-monospace, Consolas, monospace; min-width: 44px; }
  h2 { font-size: 14px; margin: 16px 0 6px; }
  code { font: 12.5px ui-monospace, Consolas, monospace; }

  /* both layouts read the same two variables */
  .box { --row: 12px; --col: 12px; row-gap: var(--row); column-gap: var(--col); outline: 2px dashed #9aa3b2; background: #fff; }
  .flex { display: flex; flex-wrap: wrap; }
  .flex > span { flex: 0 0 auto; padding: 8px 12px; border-radius: 99px; background: #dbe7ff; font-size: 14px; }
  /* the trap: three items at a third each, plus the gaps */
  .flex.thirds > span { flex: 0 0 33.333%; box-sizing: border-box; text-align: center; }

  .grid { display: grid; grid-template-columns: repeat(3, 1fr); }
  .grid > div { height: 48px; border-radius: 8px; background: #d6f2df; display: grid; place-items: center; font-size: 14px; }
  #flexNote { font-size: 13px; margin: 6px 0 0; color: #4b5563; min-height: 2.6em; }
</style>
</head>
<body>
<label>row-gap <input type="range" id="row" min="0" max="40" value="12"> <output id="rowOut">12px</output></label>
<label>column-gap <input type="range" id="col" min="0" max="40" value="12"> <output id="colOut">12px</output></label>
<label><input type="checkbox" id="thirds"> Give each tag <code>flex: 0 0 33.333%</code></label>

<h2>Flexbox with flex-wrap</h2>
<div class="box flex" id="flex">
  <span>HTML</span><span>CSS</span><span>JavaScript</span><span>Grid</span>
  <span>Flexbox</span><span>Variables</span><span>Forms</span>
</div>
<p id="flexNote"></p>

<h2>Grid, three columns</h2>
<div class="box grid" id="grid">
  <div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div>
</div>

<script>
  const boxes = document.querySelectorAll('.box');
  const flex = document.getElementById('flex');
  const note = document.getElementById('flexNote');

  function update() {
    const row = document.getElementById('row').value + 'px';
    const col = document.getElementById('col').value + 'px';
    boxes.forEach((b) => { b.style.setProperty('--row', row); b.style.setProperty('--col', col); });
    document.getElementById('rowOut').textContent = row;
    document.getElementById('colOut').textContent = col;

    flex.classList.toggle('thirds', document.getElementById('thirds').checked);
    // count how many tags share the first line
    const top = flex.children[0].offsetTop;
    const perLine = [...flex.children].filter((c) => c.offsetTop === top).length;
    note.textContent = flex.classList.contains('thirds')
      ? perLine + ' per line. Three thirds plus two gaps is wider than 100%, so the third tag wraps (set column-gap to 0 to fit three).'
      : perLine + ' tags on the first line. column-gap spaces tags on a line; row-gap spaces the lines.';
  }

  document.querySelectorAll('input').forEach((i) => i.addEventListener('input', update));
  update();
</script>
</body>
</html>
The same two values drive a flex list and a grid. Tick the box to see the percentage trap.

Older stylesheets use grid-gap, grid-row-gap and grid-column-gap. Browsers still accept those names, but they are the older grid-only spelling. Use gap in new code. For grid-specific uses, such as different space between particular rows, see CSS grid row gap.

Percentage gaps and the flex-wrap trap

A percentage column-gap is measured against the width of the container's content box, so column-gap: 5% in an 800px container is 40px.

A percentage row-gap is less useful. When the container's height comes from its content, the gap is worked out from a height that did not include it. The result is small, and the content spills out of the box.

Use a length such as px or rem for row-gap, unless the container has a set height.

The bigger trap is percentage widths plus gap. Three items at 33.333% fill the whole line on their own. Add two gaps and the total is wider than 100%, so the third item wraps. Tick the box in the example above to see it.

Subtract the gaps before dividing:

.item { flex: 0 0 calc((100% - 2 * 16px) / 3); }

CSS flex-wrap covers this and other ways to set items per row. In grid, the problem does not come up: fr units share the space that is left after the gaps. grid-template-columns shows how.

gap with margins and justify-content

gap does not replace margins that are still in the stylesheet. It adds to them. A child with margin-right: 24px in a container with gap: 30px sits 54px from its neighbour.

Left: an old margin stacks on top of the gap. Right: space-between spreads extra space, and the gap is the floor.
Left: an old margin stacks on top of the gap. Right: space-between spreads extra space, and the gap is the floor.

justify-content: space-between and gap work together. The browser first places the gaps, then shares out any space that is left. So the gap is the minimum distance between items.

On a wide screen, space-between decides the spacing. On a narrow one, the gap keeps items from touching.

A finished example: a spacing scale with CSS variables

Real pages use a few spacing sizes over and over. Put them in CSS variables and use gap for every space between elements, and padding for every space inside a box.

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>Spacing tokens with gap</title>
<style>
  /* one spacing scale, built from one unit */
  :root {
    --unit: 4px;
    --space-1: calc(var(--unit) * 1);
    --space-2: calc(var(--unit) * 2);
    --space-3: calc(var(--unit) * 3);
    --space-4: calc(var(--unit) * 4);
    --space-6: calc(var(--unit) * 6);
  }
  * { box-sizing: border-box; }
  body { margin: 0; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }

  /* the page is a column of sections: gap between them, padding around them */
  .page { display: flex; flex-direction: column; gap: var(--space-6); padding: var(--space-4); max-width: 720px; margin: 0 auto; }

  header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); }
  header strong { font-size: 18px; }
  nav { display: flex; flex-wrap: wrap; gap: var(--space-2); }
  nav a { padding: var(--space-1) var(--space-2); border-radius: 6px; background: #fff; color: inherit; text-decoration: none; font-size: 14px; }

  .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-4); }
  .card { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4); border-radius: 12px; background: #fff; }
  .card h3 { margin: 0; font-size: 15px; }
  .card p { margin: 0; font-size: 13px; color: #4b5563; line-height: 1.4; }

  form { display: grid; gap: var(--space-4); padding: var(--space-4); border-radius: 12px; background: #fff; }
  .field { display: grid; gap: var(--space-1); font-size: 14px; }
  .field input { font: inherit; padding: var(--space-2); border: 1px solid #c9ced8; border-radius: 8px; }
  .actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
  .actions button { font: inherit; font-size: 14px; padding: var(--space-2) var(--space-4); border-radius: 8px; border: 1px solid #1d2330; background: #1d2330; color: #fff; cursor: pointer; }
  .actions button[type="reset"] { background: #fff; color: #1d2330; }
  #out { margin: 0; font: 12.5px ui-monospace, Consolas, monospace; white-space: pre-wrap; color: #0f5132; }

  .density { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-size: 14px; }
  .density select { font: inherit; }
</style>
</head>
<body>
<div class="page">
  <label class="density">Density
    <select id="density">
      <option value="3px">Compact (unit 3px)</option>
      <option value="4px" selected>Default (unit 4px)</option>
      <option value="6px">Roomy (unit 6px)</option>
    </select>
  </label>

  <header>
    <strong>Studio</strong>
    <nav><a href="#work">Work</a><a href="#about">About</a><a href="#contact">Contact</a></nav>
  </header>

  <section class="cards" id="work">
    <article class="card"><h3>Brand refresh</h3><p>Logo, colours and a type scale.</p></article>
    <article class="card"><h3>Shop layout</h3><p>Product grid and checkout.</p></article>
    <article class="card"><h3>Newsletter</h3><p>A template that works on phones.</p></article>
    <article class="card"><h3>Dashboard</h3><p>Charts and filters for a team.</p></article>
  </section>

  <form id="contact">
    <div class="field"><label for="name">Name</label><input id="name" name="name" required></div>
    <div class="field"><label for="email">Email</label><input id="email" name="email" type="email" required></div>
    <div class="actions"><button type="submit">Send</button><button type="reset">Clear</button></div>
    <p id="out"></p>
  </form>
</div>

<script>
  // change one unit and every gap and padding on the page follows
  document.getElementById('density').addEventListener('change', (e) => {
    document.documentElement.style.setProperty('--unit', e.target.value);
  });

  // demo only: show what would be sent instead of sending it
  document.getElementById('contact').addEventListener('submit', (e) => {
    e.preventDefault();
    const data = new FormData(e.target);
    document.getElementById('out').textContent =
      'Would send:\n' + [...data].map(([k, v]) => k + ' = ' + v).join('\n');
  });
</script>
</body>
</html>
Header, card grid and form all spaced with gap and one --space scale. Change the density and every space follows.
  • One unit: --unit: 4px, with --space-1 to --space-6 as multiples.
  • gap between, padding inside: the page column, the nav, the card grid, each card and the form fields all use gap. No child has a spacing margin.
  • One switch: the density menu changes --unit, and every gap and padding on the page changes with it.

The form shows what it would send instead of sending it. A real form would post to a server.

When it does not work

What you see Cause Fix
gap has no effect The parent is a normal block or a table Set display: flex or display: grid on the parent
gap has no effect gap is on the items, not their parent Move it to the container
Items wrap one too early Percentage widths plus gaps exceed 100% Subtract the gaps with calc(), or use grid and fr
Space is bigger than the gap Old margins on the children add to it Remove the spacing margins
No space at the edges gap never adds outer space Add padding to the container
Items touch with space-between No gap set, and the line is full Add gap as the minimum distance
Grid is spaced, flex row is not An older browser supports gap in grid but not in flexbox Test in that browser, and use margins as the fallback if you must support it
Content spills below the box Percentage row-gap in an auto-height container Use a length for row-gap

One note on the older-browser case: @supports (gap: 1rem) does not detect flexbox support, because it answers yes for any browser that supports gap in grid.

Spacing is easier to judge when people can resize the page themselves. A screenshot shows one width, and the gaps that look right on a laptop may crowd on a phone.

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 the people you send it to can drag the sliders and switch the density themselves. If you change the spacing scale later, the same link shows the new version.

Questions people ask

Does gap work with flexbox?

Yes. On a flex container, column-gap spaces items on a line and row-gap spaces wrapped lines. With flex-direction: column, row-gap is the space between the stacked items.

Why is my CSS gap not working?

The most common cause is that the parent is a normal block, not a flex, grid or multi-column container. gap only applies to those layouts. Put display: flex or display: grid on the element that holds the items, not on the items.

What is the difference between gap and margin?

gap belongs to the container and only goes between items. Margin belongs to each item and goes on every side, including the outer edges, so it needs extra rules to remove the space at the ends. Use gap between items and padding around them.

Is grid-gap still valid?

Browsers still accept grid-gap, grid-row-gap and grid-column-gap as older names for gap, row-gap and column-gap. New code should use the short names, which also work in flexbox.

Can gap be negative?

No. A negative value is invalid and the declaration is ignored, so the gap stays at its previous value. To overlap items, use negative margins or place them in the same grid area.

Keep reading