Type something to search...
Flexbox Deep Dive: flex-grow, flex-shrink, and flex-basis

Flexbox Deep Dive: flex-grow, flex-shrink, and flex-basis

Most developers learn flexbox by copying flex: 1 onto a few elements and watching them share space. That works until it doesn't. Two items with flex: 1 end up different widths. An image refuses to shrink. A long URL pushes a sidebar off the screen. Suddenly the magic stops, and it's not obvious why.

The answer almost always lies in three properties: flex-basis, flex-grow, and flex-shrink. Together they decide how big every flex item is, and the rules behind them are more precise than they first appear. This guide walks through each one, shows the actual arithmetic the browser performs, and covers the common surprises, so that the next time a flex layout misbehaves you'll know exactly where to look.

The Big Picture

When a flex container lays out its items along the main axis (horizontally, for the default flex-direction: row), the browser follows roughly these steps:

  1. Work out each item's starting size, called the flex base size. That comes from flex-basis.
  2. Add up all the starting sizes, plus any gaps, and compare the total to the container's size.
  3. If there's space left over, distribute it according to flex-grow.
  4. If there's not enough space, take it away according to flex-shrink.
  5. Apply min and max size constraints, and repeat the distribution if any item hits a limit.

So flex-basis is where you start, and flex-grow or flex-shrink decides how you get from there to the final size. Only one of the two ever applies in a given layout pass: either the line has extra space, or it's short on space.

flex-basis: The Starting Point

flex-basis sets an item's initial main size before any growing or shrinking. It accepts the same values as width (lengths, percentages, and keywords), plus the keyword content.

.item {
  flex-basis: 200px;
}

flex-basis: auto

The default value is auto, which means "look at my width" (or height in a column layout). If width is set, that becomes the basis. If width is also auto, the basis is the item's content size: roughly how wide it would be if its content didn't wrap.

That's the source of a lot of confusion. With flex-basis: auto, an item containing a long heading starts out much wider than one containing a single word, and that difference carries through to the final layout.

flex-basis: 0

Setting flex-basis to 0 ignores content entirely. Every item starts at zero, and all the space in the container becomes free space to hand out through flex-grow. This is the key to equal-width columns, which we'll come back to shortly.

flex-basis vs. width

When both are set, flex-basis wins for the main axis, unless it's auto, in which case width is used. A handy rule is to set flex-basis when you're thinking about flex behavior, and leave width for elements that aren't flex items. Both are still limited by min-width and max-width.

flex-grow: Sharing Extra Space

flex-grow is a unitless number that says how much of the leftover space an item should take. The default is 0, meaning items don't grow at all.

Here's the arithmetic. Suppose a 900px container holds three items with a basis of 100px each:

<div class="row">
  <div class="a">A</div>
  <div class="b">B</div>
  <div class="c">C</div>
</div>
.row {
  display: flex;
  width: 900px;
}

.row > * {
  flex-basis: 100px;
}

.a {
  flex-grow: 1;
}
.b {
  flex-grow: 1;
}
.c {
  flex-grow: 2;
}
  1. Starting sizes add up to 300px.
  2. Free space is 900 - 300 = 600px.
  3. Grow factors add up to 1 + 1 + 2 = 4.
  4. Each item gets 600 * (its grow / 4) extra.

So A and B each get 150px extra and end up at 250px. C gets 300px extra and ends up at 400px.

Notice that C isn't twice as wide as A. It gets twice as much extra space. Its final width is 400px compared to 250px, because the 100px basis is the same for everyone. This is the single most common misunderstanding of flex-grow: it distributes free space, not total size.

Getting True Proportions

If you want items whose final sizes really are in a 1:1:2 ratio, set the basis to zero. Then all the space is free space, and it's divided purely by grow factor:

.row > * {
  flex-basis: 0;
}

Now free space is the full 900px, and A and B get 225px each while C gets 450px. Exactly 1:1:2.

Grow Factors Less Than 1

If the grow factors of all items on a line add up to less than 1, they don't distribute all of the free space. Each item takes that fraction of the free space:

.row > * {
  flex-basis: 100px;
  flex-grow: 0.25;
}

Three items with 0.25 add up to 0.75, so only 75% of the 600px free space is handed out (150px each), and the remaining 150px is left empty at the end of the line.

flex-shrink: Giving Space Back

flex-shrink is the opposite of grow. When the items' starting sizes add up to more than the container, it decides how much each one gives up. The default is 1, so by default every item can shrink.

The arithmetic has one important twist: shrinking is weighted by the flex basis. Larger items shrink more, even with the same shrink factor. That keeps small items from being squashed to nothing while big ones barely change.

Suppose a 500px container holds two items:

.row {
  display: flex;
  width: 500px;
}

.small {
  flex-basis: 200px;
  flex-shrink: 1;
}

.large {
  flex-basis: 400px;
  flex-shrink: 1;
}
  1. Starting sizes add up to 600px, so there's a 100px overflow.
  2. Each item's scaled shrink factor is flex-shrink * flex-basis: 200 for small, 400 for large.
  3. Those add up to 600.
  4. Small gives up 100 * 200 / 600, about 33.3px. Large gives up 100 * 400 / 600, about 66.7px.

The final sizes are about 166.7px and 333.3px. Both items shrank, but the larger one shrank twice as much, in pixels, because it started twice as large.

Preventing an Item from Shrinking

Set flex-shrink: 0 when an item must keep its size, such as an icon, an avatar, or a fixed sidebar:

<div class="media">
  <img
    class="media__avatar"
    src="/images/maria.jpg"
    alt=""
    width="48"
    height="48"
  />
  <p class="media__body">
    A long comment that should wrap without squeezing the avatar.
  </p>
</div>
.media {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
}

.media__avatar {
  flex-shrink: 0;
  border-radius: 50%;
}

Without flex-shrink: 0, a long comment can squeeze the avatar into an oval.

The Minimum Size Trap

Here's the behavior that surprises almost everyone. Flex items have a default automatic minimum size. For most items, min-width: auto on a flex item works out to its min-content width: the width of its longest unbreakable piece of content, such as a long word, a URL, an image, or a table.

That means an item won't shrink below its content, even with flex-shrink: 1. A classic symptom:

<div class="layout">
  <aside class="layout__sidebar">Sidebar</aside>
  <main class="layout__main">
    <pre><code>const veryLongLineOfCode = "this line does not wrap and keeps going past the edge of the screen";</code></pre>
  </main>
</div>
.layout {
  display: flex;
}

.layout__sidebar {
  flex: 0 0 250px;
}

.layout__main {
  flex: 1;
}

pre {
  overflow-x: auto;
}

You'd expect the pre to scroll horizontally. Instead, the whole main column grows as wide as the code line and pushes the page wider than the screen. The main column's minimum size is its min-content width, which is the entire unbreakable line.

The fix is to let the item shrink below its content:

.layout__main {
  flex: 1;
  min-width: 0;
}

Now the main column can be narrower than its content, and the pre scrolls as intended. overflow: hidden on the item has a similar effect, since items that aren't overflow: visible get a minimum size of zero, but min-width: 0 is more explicit and doesn't clip anything. In a column layout, the equivalent is min-height: 0.

If you remember one thing from this article, remember min-width: 0. It fixes a surprising number of "why won't this shrink" problems.

The flex Shorthand

The flex shorthand sets all three properties at once, in the order grow, shrink, basis:

.item {
  flex: 1 1 200px; /* grow 1, shrink 1, basis 200px */
}

The shorthand is recommended over the longhands, but its single-value forms are where the details matter. These are the common ones:

ShorthandExpands toMeaning
flex: initial0 1 autoThe default. Size from content, can shrink, won't grow.
flex: auto1 1 autoSize from content, then grow and shrink.
flex: none0 0 autoFully inflexible. Stays at its content or width.
flex: 11 1 0%Starts at zero, shares space equally.
flex: 22 1 0%Starts at zero, takes twice the share.
flex: 200px1 1 200pxStarts at 200px, grows and shrinks.

Note two things. First, a single number sets the basis to 0%, not auto. Second, if you omit the basis in any form of the shorthand, it becomes 0% too. That's different from the longhand default of auto, and it's deliberate, because it makes flex: 1 produce equal columns.

Equal Columns: Why flex: 1 Usually Works

Now we can explain why flex: 1 gives equal columns:

.columns {
  display: flex;
  gap: 1.5rem;
}

.columns > * {
  flex: 1;
}

Every item starts at a basis of zero, so the full container width (minus gaps) is free space, split evenly. Content length doesn't matter.

Compare that to flex: auto:

.columns > * {
  flex: auto;
}

With flex: auto, each item starts at its content width, and only the leftover is shared. A column with more text ends up wider. That's sometimes exactly what you want, like a nav bar where longer labels deserve more room, but it's not equal columns.

When flex: 1 Still Gives Unequal Columns

Even with flex: 1, you'll sometimes get unequal widths. The cause is nearly always the minimum size trap. If one column contains a long unbreakable string or a wide image, its min-content width is larger than its fair share, so it refuses to shrink to the equal size and the others get less. Add min-width: 0 to the items, and the columns become equal again.

Common Patterns

A Fixed Sidebar with Fluid Content

.app {
  display: flex;
}

.app__sidebar {
  flex: 0 0 16rem; /* never grow, never shrink, always 16rem */
}

.app__content {
  flex: 1;
  min-width: 0;
}

Pushing One Item to the End

Growing an empty spacer, or using an auto margin, pushes items apart:

.header {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.header__actions {
  margin-inline-start: auto;
}

Auto margins absorb whatever free space is left after flexible items have grown. That means this works as long as nothing else on the line has flex-grow; if another item grows, it takes the space first and the auto margin ends up with nothing.

Wrapping Cards with a Sensible Minimum

With flex-wrap, the basis acts like a minimum width before items wrap:

.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.cards > * {
  flex: 1 1 18rem;
}

Each card tries to be 18rem. When they don't fit, they wrap, and the ones on each line grow to fill it. The last line may have fewer, wider cards; if you'd rather keep them the same width, CSS Grid with auto-fill is the better tool.

Debugging Flex Sizing

Chrome, Edge, and Firefox all have flexbox inspectors. Select a flex container and click the flex badge in the Elements panel to see an overlay. Firefox in particular shows a breakdown for each item: its flex base size, whether it grew or shrank and by how much, and whether it was clamped by a min or max size. When the numbers don't match your expectations, that panel usually shows you why.

Conclusion

Flex sizing follows a clear process. flex-basis sets the starting size, flex-grow shares out any extra space in proportion to the grow factors, and flex-shrink takes away overflow in proportion to shrink factor times basis. Automatic minimum sizes then stop items from shrinking below their content, which is where min-width: 0 comes in.

Once you see flex: 1 as "start from zero and share equally," and flex: auto as "start from content and share the rest," most flex layouts become predictable. And when one doesn't, you now know the three places to look.

Tags :
Share :

Related Posts

A Complete Guide to CSS Container Queries

A Complete Guide to CSS Container Queries

For more than a decade, responsive design meant one thing: media queries. You asked the browser how wide the viewport was and adjusted your layout ac

Continue Reading
A Comprehensive Guide to Installing Next.js

A Comprehensive Guide to Installing Next.js

Next.js has emerged as a powerful framework for building React applications, offering features like server-side rendering, static site generation, an

Continue Reading
Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

CSS has evolved significantly, and modern tools like clamp(), min(), and max() are powerful game-changers in dynamic styling. If you’ve struggl

Continue Reading