CSS layout reference

How flexbox and grid decide sizes.

Most layout surprises come down to a default nobody wrote: a basis of auto, a minimum of auto, an alignment of stretch. This reference goes through the sizing rules of both models, and what to write when the default is not what you meant.

display

Flex or grid

Flexbox sizes from the content out: each item proposes a size, and the line shares out what is left or what is missing. Grid sizes from the container in: you define tracks, and items are placed into them. So flex suits a run of things whose sizes should follow their content, and grid suits a structure whose sizes should hold regardless of content.

Use it for

  • Flex: toolbars, nav rows, tag lists, a label beside an input, anything that should wrap like text.
  • Grid: page shells, card galleries, forms with aligned columns, anything that must line up in two directions.
  • Grid for overlap: several items placed in the same cell stack without position: absolute, and the cell sizes to the largest.
  • Both together is normal — a grid of cards whose insides are flex.

Watch for

  • A wrapping flex container is not a grid. Each line is laid out independently, so items in the last row stretch differently from the rows above.
  • Reaching for grid to get gap is no longer necessary: gap works in flexbox.
  • Neither model reorders for accessibility. order and grid placement change what is seen, not the tab or reading order.
.toolbar { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.gallery { display: grid; gap: 1rem;
           grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); }
.stack > * { grid-area: 1 / 1; }   /* overlap without absolute */

flex: grow shrink basis

The flex shorthand

flex-basis is the size an item starts from; grow and shrink are its share of the surplus or the shortfall after every basis is added up. The default, 0 1 auto, starts from the content size and never grows. flex: 1 expands to 1 1 0 — a zero basis, so all the space is shared, and items come out equal. flex: auto is 1 1 auto: only the leftover is shared, so bigger content stays bigger.

flex
Home
Documentation
Pricing and plans

1 1 auto shares the leftover space equally, so longer labels stay wider. 1 1 0 shares all the space, so the three end up the same width.

Use it for

  • flex: 1 for equal-width columns or tabs.
  • flex: auto for a nav where every item grows a little but keeps its natural proportion.
  • flex: none (0 0 auto) for icons, avatars, and buttons that must never be squeezed.
  • flex: 1 on one child to let it absorb the remaining space — the search field in a header, the main pane beside a sidebar.

Watch for

  • Equal flex values do not guarantee equal widths: items cannot shrink below their minimum content size. Add min-width: 0 when the content is long.
  • flex-basis beats width when both are set (unless basis is auto), and both yield to min-width and max-width.
  • flex-shrink is weighted by the basis, so with the same shrink factor larger items give up more pixels.
  • Writing flex-grow: 1 alone leaves the basis at auto, which is flex: auto behaviour, not flex: 1.
.tab     { flex: 1; }        /* 1 1 0    — equal widths */
.navlink { flex: auto; }     /* 1 1 auto — proportional to content */
.icon    { flex: none; }     /* 0 0 auto — never grows or shrinks */

minmax(0, 1fr)

fr units and their hidden minimum

An fr is a share of the space left over after fixed and content-sized tracks are taken out. But 1fr on its own means minmax(auto, 1fr), and that auto minimum is the track’s content minimum. A long unbreakable line raises it, the track grows to fit, and the “equal” columns are no longer equal. minmax(0, 1fr) removes the floor.

grid-template-columns
short
npm install --save-dev @types/react-dom@latest

The dashed box is the grid. Plain 1fr cannot go below the content’s minimum, so the second column takes what it needs and the columns stop being equal. With minmax(0, 1fr) they are, and the long line scrolls inside its cell.

Use it for

  • minmax(0, 1fr) for any column that holds user content, code, tables, or truncating text.
  • A fixed or content track beside a flexible one: 16rem minmax(0, 1fr), or auto minmax(0, 1fr).
  • Ratios: 2fr 1fr splits the leftover two to one, after gaps are subtracted.

Watch for

  • fr shares out leftover space, so it is not a percentage: 1fr 1fr with a gap is two equal columns that fit, 50% 50% with a gap overflows.
  • Percentages and fr inside an auto-sized grid resolve against a size that depends on them; keep fr for grids whose own width is definite.
  • fr cannot be used inside calc(), and a minmax() minimum cannot be an fr.
.shell   { display: grid; grid-template-columns: 16rem minmax(0, 1fr); }
.halves  { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }

repeat(auto-fit, …)

auto-fit versus auto-fill

Both make as many columns as fit, which gives a responsive grid with no media queries. They differ only when there are fewer items than columns. auto-fill keeps the empty tracks, so items hold their size and the row has a gap at the end. auto-fit collapses empty tracks to zero, so the items that do exist stretch across the full width.

repeat()
item 1
item 2

With few items, auto-fill keeps the empty columns and the items stay small; auto-fit collapses them and the items stretch. Add items until the row is full and the two are identical.

Use it for

  • auto-fill for galleries and card lists: one leftover card stays card-sized instead of becoming a banner.
  • auto-fit for a small, known set that should always span the row — a stats strip, a pricing table.
  • minmax(min(100%, 14rem), 1fr) as the track, so a container narrower than the minimum gets one column rather than an overflow.

Watch for

  • Without the min(100%, …) guard the minimum track width overflows narrow containers — the most common bug in this pattern.
  • The column count follows the container’s width, so it responds to the container, not the viewport — usually what you want, occasionally a surprise inside a sidebar.
  • Auto-repeat needs a definite track size. It cannot be combined with auto or fr as the minimum.
.cards {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
}

fit-content

Intrinsic size keywords

Three keywords size a box from its content. min-content is the narrowest it can go without overflowing — the longest word. max-content is its width with no wrapping at all. fit-content is the useful one: as wide as the content wants, but never wider than the space available — the way a float or an inline-block has always behaved.

width

Sized by its content, up to the space available.

Use it for

  • width: fit-content with margin-inline: auto to centre a block that should hug its content, such as a button or a tag.
  • grid-template-columns: max-content 1fr for a label column exactly as wide as its longest label.
  • fit-content(20rem) as a grid track: content-sized up to a cap.
  • min-content on a figure so the caption wraps to the width of the image.

Watch for

  • max-content ignores the container, so it overflows on narrow screens. Prefer fit-content unless you mean it.
  • These keywords are what min-width: auto resolves to in flex and grid — the same minimum content size behind most refusal-to-shrink bugs.
  • As a height they do little in normal flow, where height is content-driven already.
.tag   { width: fit-content; margin-inline: auto; }
.form  { display: grid; grid-template-columns: max-content minmax(0, 1fr); }
figure { width: min-content; }

align-items

Stretching and alignment

The default alignment in both models is stretch: items fill the cross axis of their line or the full area of their grid cell. That is why cards in a row come out the same height for free — and why an image in a flex row is distorted, and a sticky sidebar in a grid never sticks.

Use it for

  • Leave stretch alone for equal-height cards; push a card’s footer down with margin-top: auto on it.
  • align-items: start on rows that hold images, buttons, or anything with a natural height.
  • align-self: start on a sticky grid or flex child, so it is shorter than its track and has room to travel.
  • margin-inline-start: auto on one flex item to push it and everything after it to the far end.

Watch for

  • A stretched item with an explicit height, or an image with an intrinsic ratio, stops stretching or distorts — set align-self on it deliberately.
  • justify-content does nothing once any item has flex-grow, because there is no free space left to distribute.
  • place-items: center shrinks grid items to their content, which removes the equal heights stretch was giving you.
.card         { display: flex; flex-direction: column; }
.card .footer { margin-top: auto; }
.sidebar      { position: sticky; top: 1rem; align-self: start; }

Start from the symptom

I cannot decide between flexbox and grid
Flex or grid
flex: 1 items are not the same width
The flex shorthand
Two 1fr columns are not equal
fr units and their hidden minimum
One long line pushes the layout wide
fr units and their hidden minimum
A lone card stretches across the whole row
auto-fit versus auto-fill
A box should be only as wide as its content
Intrinsic size keywords
Images in a flex row are distorted
Stretching and alignment
A sticky sidebar in a grid never sticks
Stretching and alignment

Keep going

Sizes that respond to the container, not the screen.

The container query reference covers @container, container units, and fluid values with clamp().

Open the container query reference

We'd like to use Google cookies to understand how Nodlume is used and to measure our advertising. Nothing loads until you choose, and declining does not affect anything in the app.