Home / Articles / Layout /

layoutflexbox

Flex-grow, flex-shrink, and flex-basis demystified

The flex shorthand packs three values into one declaration. Understanding each one individually is the key to predictable flex layouts.

What flex-basis actually sets

flex-basis defines the initial main size of a flex item before any growing or shrinking happens. It is not the same as width. When set to auto (the default), the browser uses the item's width or height (depending on the flex direction). When set to a length, it overrides the main-axis dimension entirely.

/* flex-basis overrides width on the main axis */
.item {
  width: 200px;       /* ignored when flex-basis is set */
  flex-basis: 300px;  /* this wins — item starts at 300px */
}

/* auto means "use my width/height" */
.item-auto {
  width: 200px;
  flex-basis: auto;   /* item starts at 200px */
}

A common source of confusion: flex-basis: 0 means the item starts at zero size and gets all of its space from flex-grow. This is why flex: 1 (which expands to 1 1 0%) distributes space equally regardless of content width.

How flex-grow distributes extra space

flex-grow controls how leftover space in the flex container is distributed among items. The algorithm works in two steps: first, lay out every item at its flex-basis. Then divide the remaining space proportionally by each item's flex-grow value.

/* Container is 900px wide */
.a { flex: 1 1 100px; }  /* basis 100px, grow factor 1 */
.b { flex: 2 1 100px; }  /* basis 100px, grow factor 2 */
.c { flex: 1 1 100px; }  /* basis 100px, grow factor 1 */

/*
  Total basis: 300px
  Remaining: 900 - 300 = 600px
  .a gets 600 × (1/4) = 150px  →  total 250px
  .b gets 600 × (2/4) = 300px  →  total 400px
  .c gets 600 × (1/4) = 150px  →  total 250px
*/

Setting flex-grow: 0 (the default) means the item never grows beyond its basis. This is the behaviour you want for fixed-width icons or labels sitting next to a flexible content area.

How flex-shrink removes overflow

When the combined flex-basis values exceed the container width, flex-shrink decides how items give up space. The shrinking is weighted by both the shrink factor and the item's basis — larger items shrink more in absolute terms.

/* Container is 600px wide */
.sidebar { flex: 0 0 250px; }  /* shrink 0 — refuses to shrink */
.main    { flex: 1 1 500px; }  /* shrink 1 — will absorb the deficit */

/*
  Total basis: 750px — overflow is 150px
  .sidebar shrink factor is 0, so it stays at 250px
  .main absorbs all 150px deficit → ends at 350px
*/
Use flex-shrink: 0 on fixed-width elements like sidebars and icons so they never collapse below their intended size.

The flex shorthand and its hidden defaults

The flex shorthand is deceptively tricky because its defaults change depending on how many values you provide.

/* Writing just one value — the rest get SPECIAL defaults */
flex: 1;           /* → flex: 1 1 0%   (basis becomes 0%, not auto!) */
flex: 30px;        /* → flex: 1 1 30px  (grow becomes 1, not 0!) */

/* Writing all three — no surprises */
flex: 0 1 auto;    /* the initial value of the flex property */
flex: 1 1 0%;      /* equal-width columns */
flex: 0 0 200px;   /* fixed 200px, no grow, no shrink */

/* The keyword values */
flex: auto;        /* → flex: 1 1 auto  — grow from content size */
flex: none;        /* → flex: 0 0 auto  — fully rigid */
flex: initial;     /* → flex: 0 1 auto  — shrink only */

The safest practice is to always write all three values explicitly. This eliminates any guesswork about which defaults the shorthand applies.

flex-basis: 0 vs auto — the equal-width trap

The most common gotcha: you want three columns of equal width, so you write flex: 1 on each. It works — until one column has more content. With flex-basis: 0% (what flex: 1 expands to), all space is distributed from zero, so columns end up equal. With flex-basis: auto, items start at their content width, and only the leftover space is divided equally.

/* Equal-width columns regardless of content */
.col { flex: 1 1 0%; }

/* Proportional columns that respect content */
.col { flex: 1 1 auto; }

/* If equal-width columns still overflow,
   add min-width: 0 to allow text truncation */
.col {
  flex: 1 1 0%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
Flex items have an implicit min-width: auto which prevents them from shrinking below their content. Override it with min-width: 0 when you need items to truncate.

Practical patterns

These real-world patterns show how the three values work together.

/* Sidebar + main content */
.layout {
  display: flex;
  gap: 2rem;
}
.layout__sidebar { flex: 0 0 16rem; }
.layout__main    { flex: 1 1 0%; min-width: 0; }

/* Icon + text row where icon never shrinks */
.row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.row__icon { flex: 0 0 1.5rem; }
.row__text { flex: 1 1 0%; }

/* Three equal cards that wrap on small screens */
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.card { flex: 1 1 18rem; }

The card pattern uses a flex-basis of 18rem as a minimum comfortable width. Cards grow equally to fill the row but wrap to a new line once they would go below 18rem.

Debugging flex sizing

When a flex item ends up at an unexpected size, check these things in order:

  • What is the resolved flex-basis? Open DevTools and look at the computed flex-basis. If it says 0% but you expected auto, the shorthand changed it.
  • Is min-width preventing shrinking? Images and long words create an implicit minimum. Set min-width: 0 or overflow: hidden.
  • Is the container itself constrained? A flex container inside another flex container inherits its own sizing constraints — check the parent too.
/* Quick debug: outline every flex item */
.debug-flex > * {
  outline: 2px solid oklch(0.7 0.2 330);
}

/* Show computed sizes in a pseudo-element (dev only) */
.debug-flex > *::after {
  content: attr(style);
  display: block;
  font-size: 0.7rem;
  color: oklch(0.6 0.02 260);
}