flex-wrap and the mythical “line”: how the spec actually packs items
Most flex tutorials hand-wave flex-wrap: wrap as “items go to a new row when they run out of space.” Close, but the actual behaviour is stranger and worth knowing the day a stubborn card grid refuses to balance.
What the spec calls a line
The CSS Flexible Box Layout Module Level 1 defines a flex line as a group of items collected together during line breaking. With flex-wrap: nowrap there is exactly one line, no matter how much overflow you produce. With wrap the algorithm packs items greedily until the next item’s hypothetical main size would exceed the container’s inner main size, then starts a new line.
Crucially, each line then runs the flex sizing algorithm independently. Two lines with three items each are not a 3×2 grid. They are two separate flex containers stacked along the cross axis.
Why your last row looks weird
If three items fit per line and you have seven items, the last line has one item. That single item gets the full line’s main size to negotiate over and grows to fill it — unless flex-grow is zero. This is the source of the classic “why is my last card huge” bug. Fixes:
- Set
flex-grow: 0and a fixedflex-basis, so items keep their size. - Use
justify-contenton the container to control where the orphan sits. - Switch to CSS Grid with
repeat(auto-fill, minmax(...)), which keeps cell sizes constant.
wrap-reverse is not just cosmetic
Setting flex-wrap: wrap-reverse flips the cross-axis direction. The first line ends up at the bottom (or right, with column flow). This matters for keyboard order: tab order follows the source, not the visual order. Rachel Andrew flagged this in her Smashing piece “Flexbox: How Big Is That Flexible Box?” (2018) — visual reverse is a 1.3.2 WCAG concern, the same trap order sets.
gap and wrap interact
When wrapping is on, the row-axis component of gap applies between lines, not just between items in a line. gap: 16px 8px on a wrapping row gives 16px between rows and 8px between items. Flip flex-direction to column and the meanings swap, because the row/column axes follow the writing-mode-relative main and cross axes.
The single-item line trick
Want a sticky header inside a wrapping flex layout? Give the header flex-basis: 100%. It consumes a full line on its own, then everything else wraps below. Cleaner than a wrapper div for hero rows above a card grid.
Related
- gap is the flex property you keep forgetting
- align-content: the property tutorials skip
- order considered harmful: when flex visual order breaks tab order
Get FlexFrog on the App Store. Built by Moruk Labs.