Web Platform · CSS Layout

Flex Basis, Free Space, and the Grow-Shrink Algorithm

How a flex container assigns base sizes, computes free space along the main axis, and resolves growth and shrinkage when items are clamped by minimums.

Two flex items with flex: 1 and different content often end up different widths, and two items with flex: 1 and flex-basis: 0 usually do not. The difference is not rounding. Flex layout resolves sizes through a multi-step algorithm in which a base size is chosen first, available space is measured against that base, and only the leftover is distributed by the flexibility factors. Changing the base changes the starting point of the distribution, and the same factor applied to a different starting point produces a different result.

Reading the algorithm in order is the only reliable way to predict what a flex container will do. The axes come first, then the base sizes, then the free space, then the distribution, then clamping, and finally alignment.

Axes and the two sizing directions

A flex container’s main axis is the direction in which items are placed, set by flex-direction: row places the main axis along the inline axis, column along the block axis. The cross axis is perpendicular. Every flex property that mentions “main” or “cross” refers to these axes, so flex-grow affects the inline direction in a row container and the block direction in a column container.

The container’s main size is definite if it can be resolved from its own sizing; otherwise it is content-sized and the algorithm runs against an indefinite main size, which changes the outcome for growth. A container with display: flex and width: auto as a block-level box takes the containing block’s inline size, so its main size is definite. A container that is itself a flex item in a content-sized row may have an indefinite main size, and its items then size to their content.

Cross-axis sizing is a separate pass. The container’s cross size is determined after the items’ hypothetical cross sizes are known, and align-items, align-self and align-content operate within that.

Flex base size and the hypothetical main size

The first pass computes each item’s flex base size. The rule depends on flex-basis:

  • A length or percentage sets the base directly. A percentage resolves against the container’s inner main size and is treated as content if that size is indefinite.
  • auto defers to the item’s width (or height, in a column), and if that is also auto, the base is the item’s max-content size.
  • content uses the item’s content size, ignoring width.
  • The initial value of flex-basis is auto, so an item with no flex declarations and an explicit width starts from that width.

The base size is then clamped by the item’s min-width and max-width to produce the hypothetical main size. The distinction matters during free-space calculation: the base sizes are summed to measure free space, but the hypothetical sizes are used when deciding whether the line overflows or has room.

The flex shorthand expands to three values. flex: 1 means 1 1 0%, flex: auto means 1 1 auto, and flex: none means 0 0 auto. The 0% basis in flex: 1 is why equally grown items become equal: the free space is measured after subtracting a zero base, so all of the container’s inner size is free and is divided by the grow factors. With flex: auto, the base is the content size, and equal grow factors add equal shares of the remaining space to unequal bases, preserving the difference.

/* Equal columns, even when content is wider than its share. */
.equal > * { flex: 1 1 0%; min-width: 0; }

/* Columns that keep their content width and share what is left. */
.proportional > * { flex: 1 1 auto; }

/* A fixed sidebar that never shrinks, plus a fluid main area. */
.layout > .side { flex: 0 0 16rem; }
.layout > .main { flex: 1 1 0%; min-width: 0; }

Positive free space and the grow factors

Free space is the container’s inner main size minus the sum of the items’ outer base sizes, minus the gaps between them. If it is positive, and the sum of the flex-grow values is at least 1, each item receives free space × flex-grow / sum of grow factors.

If the sum of the grow factors is less than 1, only that fraction of the free space is distributed: two items with flex-grow: 0.25 each receive a quarter of the free space, so half of it is consumed and the rest stays as empty space at the end of the line, subject to justify-content. This behaviour is specified and is occasionally useful for partial growth, but it surprises anyone who expects factors to be normalised.

Growth is applied to the base size, not to the hypothetical size, and the result is then clamped by the item’s max size. Clamping creates the first feedback loop in the algorithm: an item that hits max-width cannot absorb its share, so the remaining items are re-run with the leftover space. The specification describes this as looping until no item’s size changes. The practical consequence is that a max-width on one flex item causes the others to grow more than a naive proportional calculation predicts.

Negative free space and scaled shrink factors

When the sum of base sizes exceeds the container’s inner main size, free space is negative and the shrink factors apply. Shrinkage is not proportional to flex-shrink alone; it is proportional to the scaled shrink factor, which is flex-shrink × flex base size. The magnitude of each item’s negative adjustment is the absolute free space multiplied by its scaled factor divided by the sum of all scaled factors.

The scaling by base size keeps the result proportional to the base: an item with a 600-pixel base and flex-shrink: 1 shrinks three times as far as an item with a 200-pixel base and the same factor, because the scaled factors are 600 and 200. With the raw factors, both items would give up the same number of pixels, which overstates the loss for the narrow item and understates it for the wide one, and drives the narrow item to its minimum first.

Shrinkage is clamped by min-width, and the same loop applies: an item that reaches its minimum is frozen, and the remaining negative space is redistributed among the others. This is why a row with a wide image and several text columns can end with the text columns crushed to their minimums while the image refuses to shrink.

The automatic minimum size

For a flex item, min-width (in a row) defaults to auto, and auto does not mean zero. It resolves to the item’s content-based minimum size, which is the smaller of its specified size suggestion and its content size suggestion, where the content size suggestion is the min-content size. In effect, an item cannot shrink below its longest unbreakable word or its intrinsic width unless overflow is not visible, or min-width: 0 is declared.

The rule exists to prevent content from being clipped by the flex algorithm, which would otherwise shrink items to nothing whenever space is scarce. It is the reason the standard fix for an overflowing flex child holding long text is min-width: 0. Once the minimum is zero, the shrink factor can do its work and the text wraps or overflows according to its own overflow value.

The automatic minimum interacts with intrinsic sizing because the min-content size depends on text, fonts, and replaced-element dimensions. An item holding an image with a natural width of 1200 pixels has a content-based minimum of 1200 unless the image is given max-width: 100% or the item is given an explicit minimum. Setting overflow: hidden on the item also disables the content-based minimum, because the automatic minimum only applies when overflow is visible.

Wrapping and the cross axis

flex-wrap: wrap breaks items into lines when their hypothetical main sizes no longer fit. Each line’s main size is the container’s inner main size, and each line’s cross size is the largest outer hypothetical cross size of its items. Lines are then stacked, and align-content distributes any leftover cross space between them; with a single line, align-content has no effect and align-items governs each item’s cross position.

align-items: stretch is the initial value, and it applies only to items whose cross size is auto. Such an item is stretched to fill its line’s cross size after the line is sized, so a row of items with different content heights all reach the tallest item’s height. An item with an explicit height keeps that height and is positioned within the line by align-self.

Gaps participate in free-space calculation and never collapse. gap: 1rem on a container with ten items along the main axis subtracts nine gaps, not ten. The subtraction happens before growth or shrinkage, so a container whose gaps exceed its inner size has negative free space and items shrink; gaps themselves do not shrink.

The final alignment step is described with the other alignment properties in Alignment, Overflow, and Scroll Containers, including the difference between justify-content on a line with positive free space and auto margins on individual items.

References

  1. CSS Flexible Box Layout Module Level 1 — W3C
  2. CSS Box Alignment Module Level 3 — W3C
  3. CSS Box Sizing Module Level 3 — W3C