CSS flex-grow, flex-shrink and flex-basis, worked out

flex-grow does not set a width. It decides who gets the space left over after every item takes its flex-basis, and flex-shrink decides who pays when there is not enough.

flex-grow shares out free space. The browser first gives every flex item its flex-basis. Whatever is left in the row is split between the items in the ratio of their flex-grow values.

When the bases add up to more than the row, flex-shrink decides how much each item gives back.

Try it. Change grow, shrink and basis for three items and drag the container width. The panel shows the math, and the last line shows what the browser actually drew.

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>Flex space calculator</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
  fieldset { margin: 0; padding: 8px; border: 1px solid #d5d9e0; border-radius: 10px; background: #fff; min-width: 0; }
  legend { font-size: 12px; font-weight: 700; padding: 0 4px; }
  label { display: flex; justify-content: space-between; gap: 4px; font-size: 12px; margin: 3px 0; }
  input[type=number] { width: 52px; font: inherit; }
  .width { margin: 12px 0 8px; font-size: 13px; }
  .width input { width: 100%; }

  /* the flex container: its width comes from the slider */
  .row { display: flex; height: 56px; background: repeating-linear-gradient(45deg, #fff, #fff 6px, #eef1f5 6px, #eef1f5 12px); outline: 2px solid #1d2330; }
  .item {
    min-width: 0;  /* min-width: 0 so only the math decides */
    box-shadow: inset 0 0 0 2px #fff; border-radius: 6px; color: #fff;
    font: 700 13px/1 system-ui, sans-serif; display: grid; place-items: center; overflow: hidden;
  }
  .a { background: #2563eb; } .b { background: #0f766e; } .c { background: #c2410c; }
  pre { margin: 10px 0 0; padding: 10px; background: #fff; border-radius: 10px; font: 12px/1.55 ui-monospace, Consolas, monospace; white-space: pre-wrap; }
</style>
</head>
<body>
<div class="controls" id="controls"></div>
<div class="width">Container width: <b id="cw"></b>
  <input type="range" id="slider" min="120" step="10">
</div>
<div class="row" id="row">
  <div class="item a">A</div><div class="item b">B</div><div class="item c">C</div>
</div>
<pre id="math"></pre>

<script>
  const items = [...document.querySelectorAll('.item')];
  const start = [{ g: 1, s: 1, b: 100 }, { g: 2, s: 1, b: 60 }, { g: 0, s: 1, b: 40 }];
  const controls = document.getElementById('controls');
  const slider = document.getElementById('slider');
  const row = document.getElementById('row');

  // one small form per item: grow, shrink, basis
  start.forEach((v, i) => {
    const name = 'ABC'[i];
    controls.insertAdjacentHTML('beforeend', `<fieldset><legend>${name}</legend>
      <label>grow <input type="number" min="0" step="1" value="${v.g}" data-k="g"></label>
      <label>shrink <input type="number" min="0" step="1" value="${v.s}" data-k="s"></label>
      <label>basis <input type="number" min="0" step="10" value="${v.b}" data-k="b"></label></fieldset>`);
  });
  const sets = [...controls.querySelectorAll('fieldset')];

  // the room we have: the row's full width, capped at 560px
  slider.max = Math.min(560, Math.floor(row.getBoundingClientRect().width));
  slider.value = Math.min(360, slider.max);

  function update() {
    const W = +slider.value;
    row.style.width = W + 'px';
    document.getElementById('cw').textContent = W + 'px';

    const v = sets.map(f => {
      const o = {};
      f.querySelectorAll('input').forEach(inp => o[inp.dataset.k] = Math.max(0, +inp.value || 0));
      return o;
    });
    v.forEach((o, i) => items[i].style.flex = `${o.g} ${o.s} ${o.b}px`);

    const sum = v.reduce((t, o) => t + o.b, 0);
    const free = W - sum;
    let lines = [`free space = ${W} - (${v.map(o => o.b).join(' + ')}) = ${free}px`];

    if (free >= 0) {
      // grow totals below 1 hand out only part of the space
      const G = Math.max(1, v.reduce((t, o) => t + o.g, 0));
      lines.push(`grow shares: ${v.map(o => o.g).join(' : ')}, divided by ${G}`);
      v.forEach((o, i) => {
        lines.push(`${'ABC'[i]}: ${o.b} + ${free} x ${o.g}/${G} = ${(o.b + free * o.g / G).toFixed(1)}px`);
      });
    } else {
      // shrinking is weighted by shrink x basis
      const S = v.reduce((t, o) => t + o.s * o.b, 0);
      lines.push(`too wide by ${-free}px. weights = shrink x basis: ${v.map(o => o.s * o.b).join(' : ')}`);
      v.forEach((o, i) => {
        const w = o.b - (S ? -free * o.s * o.b / S : 0);
        lines.push(`${'ABC'[i]}: ${o.b} - ${-free} x ${o.s * o.b}/${S || 1} = ${w.toFixed(1)}px` + (w < 0 ? '  (stops at 0, the rest comes off the others)' : ''));
      });
    }

    // what the browser actually drew
    lines.push('browser: ' + items.map((el, i) => `${'ABC'[i]} ${el.getBoundingClientRect().width.toFixed(1)}`).join(', '));
    document.getElementById('math').textContent = lines.join('\n');
  }

  controls.addEventListener('input', update);
  slider.addEventListener('input', update);
  update();
</script>
</body>
</html>
Three flex items. Change the numbers or drag the slider, and compare the math with the browser's result.

This article is about how the sizes are worked out. For a first tour of rows, gaps and alignment, start with CSS Flexbox.

Step 1: every item starts at flex-basis

flex-basis is the size an item starts from before any sharing. It can be a length like 120px, a percentage, or auto.

With auto, the browser uses the item's width if it has one, and its content size if not. Then it subtracts all the bases from the container's width. The result is the free space. It can be positive (room left) or negative (too wide).

Step 2: flex-grow shares the free space

When free space is positive, each item gets free space x its grow / total grow. Only the free space is shared, not the whole width.

600px row, three 100px bases, grow 1 : 2 : 0. The 300px of free space is split 100 and 200.
600px row, three 100px bases, grow 1 : 2 : 0. The 300px of free space is split 100 and 200.

That is why flex-grow: 2 does not make an item twice as wide as one with flex-grow: 1. In the picture, B ends at 300px and A at 200px, because both started at 100px.

Two edge cases:

  • flex-grow: 0 is the default. The item keeps its basis and takes nothing. If every item has 0, the free space stays empty at the end of the row.
  • Grow values that add up to less than 1 only hand out that fraction. One item with flex-grow: 0.5 takes half the free space, and the other half stays empty.

Step 3: flex-shrink removes overflow, weighted by size

When the bases add up to more than the container, items shrink. The default is flex-shrink: 1, so they all shrink unless told otherwise.

The cut is not split equally. Each item's share is flex-shrink x its basis, so a 200px item gives up twice as much as a 100px item.

500px of bases in a 300px row. Equal cuts would crush A. Weighting by basis keeps every item at 60% of its start.
500px of bases in a 300px row. Equal cuts would crush A. Weighting by basis keeps every item at 60% of its start.

Strictly, the weight uses the basis without padding and border. In the calculator above, items have no padding or border, so the simple numbers match the browser exactly.

flex-shrink: 0 opts an item out. It keeps its basis and the others give up more. If nothing can shrink, the row overflows.

flex: 1 vs flex: auto vs flex: none

The flex shorthand sets all three values at once. The single-number form also sets the basis to 0%, and that changes the result.

Shorthand Expands to Starts from Result
flex: 1 1 1 0% Zero Equal widths
flex: auto 1 1 auto Content or width Wider content stays wider
flex: none 0 0 auto Content or width Never grows or shrinks
flex: 0 0 200px 0 0 200px 200px A fixed 200px item
no flex set 0 1 auto Content or width Shrinks, never grows
flex: 1 starts both items at zero, so they end equal. flex: auto starts them at their content and shares only the rest.
flex: 1 starts both items at zero, so they end equal. flex: auto starts them at their content and shares only the rest.

The same three labels in each row show the difference:

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>flex: 1 vs flex: auto vs flex: none</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  h3 { margin: 14px 0 6px; font: 700 14px ui-monospace, Consolas, monospace; }
  h3 span { font: 400 12px system-ui, sans-serif; color: #5b6270; }
  .row { display: flex; gap: 6px; padding: 6px; background: #fff; border: 1px solid #d5d9e0; border-radius: 10px; }
  .row div { padding: 10px 8px; border-radius: 6px; background: #dbeafe; font-size: 13px; }
  .row div small { display: block; margin-top: 4px; font: 700 11px ui-monospace, Consolas, monospace; color: #1e40af; }

  .one  div { flex: 1; }     /* = 1 1 0%   : start from zero, share everything */
  .auto div { flex: auto; }  /* = 1 1 auto : start from the content, share the rest */
  .none div { flex: none; }  /* = 0 0 auto : content width, never grow or shrink */
</style>
</head>
<body>
<h3>flex: 1 <span>equal widths</span></h3>
<div class="row one"><div>Hi</div><div>A much longer label</div><div>Medium text</div></div>

<h3>flex: auto <span>longer content gets a wider box</span></h3>
<div class="row auto"><div>Hi</div><div>A much longer label</div><div>Medium text</div></div>

<h3>flex: none <span>boxes hug their content</span></h3>
<div class="row none"><div>Hi</div><div>A much longer label</div><div>Medium text</div></div>

<script>
  // print each box's rendered width inside it
  function label() {
    document.querySelectorAll('.row div').forEach(d => {
      let s = d.querySelector('small');
      if (!s) { s = document.createElement('small'); d.append(s); }
      s.textContent = Math.round(d.getBoundingClientRect().width) + 'px';
    });
  }
  addEventListener('resize', label);
  label(); label();  // second pass: the labels themselves add a little width
</script>
</body>
</html>
Same content, three shorthands. The width of each box is printed inside it.

Use flex: 1 for equal columns and flex: auto when items should keep the proportions of their content. Use flex: none for buttons and icons that must stay their natural size.

flex-basis vs width

When flex-basis is not auto, it wins over width in the main direction. An item with width: 300px; flex-basis: 100px starts at 100px.

min-width and max-width still apply after growing and shrinking. An item with flex: 1; max-width: 100px stops at 100px, and the space it did not take goes to the other items. Max width in CSS covers those limits in more detail.

In a flex-direction: column container, all of this happens with height instead of width.

Why items refuse to shrink: min-width: auto

A flex item has min-width: auto by default. For a flex item, this means "no narrower than my content". A long word, a URL, an image or a text input sets a floor, and flex-shrink stops there.

.main { flex: 1; min-width: 0; }   /* allowed to shrink below its content */

overflow: hidden on the item also removes the floor. This is the same fix that text-overflow: ellipsis needs inside a flex row.

Three layouts that use it

These are the patterns that come up most. Drag the slider to narrow all three.

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>Three flex-grow layouts</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .size { font-size: 13px; margin-bottom: 10px; }
  .size input { width: 100%; }
  #stage > div + div { margin-top: 14px; }
  h3 { margin: 0 0 6px; font-size: 13px; color: #5b6270; font-weight: 600; }

  /* 1. search bar: the input takes the rest of the row */
  .search { display: flex; gap: 6px; }
  .search input { flex: 1; min-width: 0; padding: 9px 10px; font: inherit; border: 1px solid #c9cdd4; border-radius: 8px; }
  .search button { flex: none; padding: 9px 14px; font: inherit; border: 0; border-radius: 8px; background: #2563eb; color: #fff; }

  /* 2. sidebar keeps its width, main grows */
  .page { display: flex; height: 110px; gap: 6px; }
  .side { flex: 0 0 120px; background: #0f766e; }
  .main { flex: 1; min-width: 0; background: #fff; border: 1px solid #d5d9e0; }
  .side, .main { border-radius: 8px; padding: 10px; font-size: 13px; box-sizing: border-box; }
  .side { color: #fff; }

  /* 3. toolbar: the title shrinks, the button never does */
  .bar { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: #1d2330; color: #fff; border-radius: 8px; }
  .title { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 14px; }
  .save { flex-shrink: 0; padding: 7px 12px; border: 0; border-radius: 6px; background: #22c55e; color: #fff; font: 600 13px system-ui, sans-serif; }
</style>
</head>
<body>
<div class="size">Width of the three examples: <b id="w"></b>
  <input type="range" id="slider" min="200" step="10">
</div>

<div id="stage">
  <div>
    <h3>Search bar</h3>
    <form class="search" id="form">
      <input name="q" placeholder="Search notes" aria-label="Search">
      <button>Search</button>
    </form>
  </div>

  <div>
    <h3>Sidebar + main</h3>
    <div class="page">
      <div class="side">Sidebar<br>120px</div>
      <div class="main" id="main">Main</div>
    </div>
  </div>

  <div>
    <h3>Toolbar</h3>
    <div class="bar">
      <div class="title">Quarterly planning notes for the design review</div>
      <button class="save">Save</button>
    </div>
  </div>
</div>

<script>
  const slider = document.getElementById('slider');
  const stage = document.getElementById('stage');
  const main = document.getElementById('main');

  slider.max = Math.min(600, Math.floor(stage.getBoundingClientRect().width));
  slider.value = slider.max;

  function resize() {
    stage.style.width = slider.value + 'px';
    document.getElementById('w').textContent = slider.value + 'px';
    main.textContent = 'Main: ' + Math.round(main.getBoundingClientRect().width) + 'px';
  }
  slider.addEventListener('input', resize);
  resize();

  // demo only: show the query instead of sending it
  document.getElementById('form').addEventListener('submit', (e) => {
    e.preventDefault();
    const q = new FormData(e.target).get('q');
    e.target.querySelector('input').placeholder = q ? 'Searched: ' + q : 'Type something first';
    e.target.reset();
  });
</script>
</body>
</html>
A search input that fills the row, a fixed sidebar with a growing main area, and a toolbar where the Save button never shrinks.
/* Search bar: the input takes the rest of the row */
.search input  { flex: 1; min-width: 0; }
.search button { flex: none; }

/* Sidebar keeps 120px, main takes the rest */
.side { flex: 0 0 120px; }
.main { flex: 1; min-width: 0; }

/* Toolbar: the title gives way, the button does not */
.title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.save  { flex-shrink: 0; }

Each layout follows one rule: give flex: 1 and min-width: 0 to the item that should stretch, and flex: none or flex-shrink: 0 to the item that must not change.

When it does not work

What you see Cause Fix
flex-grow has no effect The property is on the container, not the items Put flex or flex-grow on the children
flex-grow has no effect The container is only as wide as its content, so there is no free space Give the container a width, or use display: flex instead of inline-flex
The item stops growing early A max-width on the item Remove or raise the max-width
An item will not shrink min-width: auto keeps it at its content size min-width: 0 on the item
An item will not shrink flex-shrink: 0 or flex: none Set flex-shrink: 1
Columns are unequal flex: auto starts each one from its content Use flex: 1
width is ignored flex-basis is set and not auto Set the size with flex-basis
Grow 2 is not twice as wide as grow 1 Grow shares only the free space Use flex-basis: 0, or flex: 1 and flex: 2

If items line up correctly but sit at the wrong end of the row, the property you want is justify-content.

Flex sizing is easier to understand by dragging a width than by reading numbers. A screenshot shows one width only, and an .html file sent as an attachment may open as plain code on a phone.

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

Questions people ask

What does flex: 1 mean?

It is short for flex-grow: 1, flex-shrink: 1, flex-basis: 0%. Every item starts from zero and takes an equal share of the whole row, so the columns come out equal whatever their content, as long as the content fits.

What is the difference between flex: auto and flex: 1?

flex: auto is 1 1 auto. Each item starts at its content (or width) size and only the leftover space is shared equally, so items with more content stay wider. flex: 1 starts every item at zero, so the widths come out equal.

What does flex-grow: 0 do?

The item does not take any free space. It stays at its flex-basis. 0 is the default, which is why flex items do not stretch across the row until you give them a grow value.

Why is my flex item not shrinking?

Flex items have min-width: auto, which stops them shrinking below their content, such as a long word, an image or an input. Set min-width: 0 (or overflow: hidden) on the item. Also check that it does not have flex-shrink: 0 or flex: none.

Does flex-basis override width?

Yes, when flex-basis is anything other than auto. width is only used as the starting size when flex-basis is auto. min-width and max-width still apply on top of both.

Keep reading