skip to content

In a wrapped flex row of cards where each card is flex: 1 1 240px, the cards on the final row render much wider than the rows above. Why does that happen, and how do you get consistent card widths?

level: seniorimportance: should knowfreq 38%

answer

  1. free space is a per-line budget
  2. fewer items means a bigger share
  3. flexbox is one-dimensional by design
  4. cap it with no-grow or max-width
  5. columns across rows want a grid

basics

~20 s

Flexbox distributes free space per flex line, not across the whole container. The last line holds fewer cards, so each one grows more. Cap the growth with flex-grow: 0 or a max-width, or switch to a two-dimensional layout.

solid answer

~50 s

Once items are collected into lines, each line resolves its flexible lengths on its own, so `flex-grow: 1` hands the leftover space of *that line* to *its* items. A full row splits the remainder four ways; a final row with one card gives that card everything. It is working as specified — flex is a one-dimensional system and has no concept of a column shared across rows. The fixes, in order of bluntness: set `flex: 0 1 240px` and use `justify-content` to place the short last row; keep growth but add `max-width` so a card cannot exceed a sane size; or, if identical column widths across rows are an actual requirement, use CSS Grid with `repeat(auto-fill, minmax(240px, 1fr))`, which is genuinely two-dimensional. The old trick of appending invisible filler elements after the real ones still works but adds markup for a purely visual concern.

code

css · 11 lines
css
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  justify-content: flex-start;
}

.cards > .card {
  flex: 0 1 240px;
  max-width: 360px;
}

go deeper

for a junior

Recognise the symptom and know that the cards are stretching because flex-grow is non-zero. Being able to say that removing grow makes every card its base width is enough here.

for a middle

Explain that lines are resolved independently, so a line with fewer items gives each item a larger share of the leftover space, and describe the flex-grow: 0 and max-width fixes and what each costs.

for a senior

Diagnose from the symptom that item count is not a multiple of the columns, then choose deliberately between capping growth and moving to a two-dimensional layout because the requirement is column alignment.

for a principal

Set the house rule: decide when a component's requirement is one-dimensional versus two-dimensional, and avoid a codebase where every card list carries a different ad-hoc patch for the same well-understood behaviour.

## The mechanism A wrapping flex container does its work in two passes. First it collects items into **flex lines**, using each item's hypothetical main size (here `240px`, from the `flex-basis` component of `flex: 1 1 240px`) plus any `gap`. Then it resolves flexible lengths — and it does that **line by line**, treating each line as an independent single-line flex context. So consider a 1000px container with `gap: 20px` and cards of basis `240px`. Three cards plus two gaps come to 760px; a fourth would need another 260px, reaching 1020px, so three cards fit per line. On a full line the leftover is 1000 − 760 = 240px, split three ways: each card renders 320px. If there are seven cards, the last line holds one, its leftover is 1000 − 240 = 760px, and with `flex-grow: 1` that single card absorbs all of it and renders 1000px wide — more than three times its neighbours above. Nothing is broken. Flexbox is explicitly a one-dimensional layout system: it aligns items along one axis and has no notion of a column that persists across lines. Ragged final rows are the direct consequence of that design. ## Fix 1: stop growing The simplest correction is to remove growth entirely and let the leftover space sit at the end of each line: ```css .cards { display: flex; flex-wrap: wrap; gap: 20px; justify-content: flex-start; } .cards > .card { flex: 0 1 240px; /* no grow, may shrink, base 240px */ } ``` Every card is now 240px on every line, and the short last line simply ends early. `justify-content` decides what happens to the slack — `flex-start` leaves it at the end, `center` centres each line's contents, and `space-between` is usually the wrong choice here because it will spread a two-card last line to the container's full width. The trade is that the layout no longer fills the container edge to edge; the right margin ripples as the viewport changes. Whether that matters is a design decision. ## Fix 2: grow, but with a ceiling If you want the cards to absorb slack on full rows but not run away on short ones, keep `flex-grow` and clamp the result: ```css .cards > .card { flex: 1 1 240px; max-width: 360px; /* growth stops here */ } ``` `max-width` clamps the item after flexing, so a lone card on the last line stops at 360px instead of stretching to 1000px. The last row is still not aligned to the columns above — the cards are just no longer absurd. This is often the pragmatic middle ground for card grids where exact column alignment is not a hard requirement. ## Fix 3: use a two-dimensional layout If the requirement is genuinely "every card in every row has the same width, and they line up in columns", that is a two-dimensional requirement and CSS Grid expresses it directly: ```css .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 20px; } ``` The track list is computed once for the container, so every row uses the same columns and a short final row leaves empty tracks rather than inflating its items. Recognising *which* system the requirement calls for is the point of the question; a candidate who can only patch the flex version has not really diagnosed it. ## The filler-element trick, and why to be careful A long-standing workaround is to append several zero-height, invisible elements after the real cards with the same `flex-basis`, so the last line is padded out and the real cards keep their width. It works, and it predates widespread grid support. The costs are real though: extra nodes that mean nothing, a count that must be at least one less than the number of columns and therefore tied to your breakpoints, and elements that must be kept out of the accessibility tree. Reach for it only when a two-dimensional layout is genuinely unavailable. ## Diagnosing it in the wild The symptom is distinctive: everything looks right until the item count stops being a multiple of the columns per row. That is the tell that you are looking at per-line free-space distribution rather than a sizing bug in the card itself. A quick check is to give the cards `flex-grow: 0` in devtools — if the last row snaps back to matching widths, the diagnosis is confirmed and you are now only choosing which of the three fixes fits the design.

  • Would setting align-content or justify-content fix the wide last row?
    No. `align-content` distributes the lines themselves along the cross axis and never touches item widths. `justify-content` only matters once the items have stopped growing — while `flex-grow` is non-zero there is no leftover main-axis space for it to distribute. The width comes from growth, so growth is what you have to change.
  • Why does adding a max-width to the cards leave the last row still misaligned with the rows above?
    max-width clamps how far a card may grow, but each line still computes its own free space, so a short last line simply distributes less of it. The cards stop at the ceiling instead of matching the column positions above. Aligning across rows requires a shared track list, which is a grid concern rather than a flex one.
  • When would you still choose the wrapping flex version over a grid for a card list?
    When the cards are genuinely a one-dimensional list that happens to wrap, and content-driven widths matter more than column alignment — chips, tags, or logo rows where items have differing natural widths. Flex sizes each item from its content within a line; grid imposes a uniform track list, which is exactly wrong for ragged, intrinsically sized content.

saying these in an interview costs you the question

  • Blames gap or align-content for the wide final row
  • Thinks free space is shared across the whole container
  • Adds a media query per breakpoint instead of capping growth
  • Believes flex-basis alone fixes an item's final width
  • Says flexbox aligns items into columns across wrapped rows

context