Cheatsheet
Flexbox and Grid, side by side, with recipes
Both layout systems in one page: container properties, item properties, the full alignment matrix, the sizing keywords nobody remembers, and eight recipes you can paste straight into a stylesheet. Flexbox and Grid are listed next to each other on purpose, because the hard part is never the syntax - it is knowing which one to reach for.
Flexbox or Grid: decide in one sentence
Flexbox distributes items along one axis and lets content decide the sizes. Grid defines the sizes first, in two dimensions, and drops content into them. If you find yourself writing widths on flex children, you wanted Grid; if you find yourself writing a one-column grid, you wanted Flexbox.
| You are building | Reach for | Why |
|---|---|---|
| A nav bar, toolbar, or button row | flex | One axis, items sized by their own content, spacing pushed with auto margins. |
| A page shell with header, sidebar, main, footer | grid | Two dimensions at once, and named areas make the markup order irrelevant. |
| A card grid that reflows on its own | grid | One line of auto-fit plus minmax replaces four media queries. |
| A form row where one field takes the slack | flex | flex: 1 on the field that should absorb leftover space. |
| Centering one thing in a box | grid | place-items: center is two words and handles both axes. |
| Rows that must line up across separate cards | grid + subgrid | Subgrid is the only way a child inherits the parent's tracks. |
Flexbox container properties
Set these on the element that has display: flex. Defaults are called out because most flex bugs are a default you forgot about.
| Property | Values (default first) | What it controls |
|---|---|---|
| display | flex | inline-flex | Makes children flex items. Inline-flex keeps the box itself inline. |
| flex-direction | row | row-reverse | column | column-reverse | Which way the main axis runs. Everything else is defined relative to this. |
| flex-wrap | nowrap | wrap | wrap-reverse | Whether items overflow on one line or break onto new lines. |
| flex-flow | row nowrap | Shorthand for direction plus wrap, in that order. |
| justify-content | flex-start | center | flex-end | space-between | space-around | space-evenly | Distribution of items along the MAIN axis, once they are sized. |
| align-items | stretch | flex-start | center | flex-end | baseline | Position of every item on the CROSS axis. Stretch is why unequal cards match height. |
| align-content | normal | flex-start | center | space-between | stretch | Spacing BETWEEN wrapped lines. Does nothing without flex-wrap: wrap. |
| gap | normal | length | row-gap column-gap | Space between items without margin hacks. Works in Flexbox everywhere now. |
| row-gap | normal | length | Gutter between wrapped lines only. |
| column-gap | normal | length | Gutter between items on the same line only. |
| place-content | align-content justify-content | Shorthand. One value sets both. |
Gotcha: justify-items does nothing in Flexbox. There is no such thing as per-item main-axis alignment - use an auto margin instead.
Flexbox item properties
Set these on the children. The flex shorthand is worth memorizing because the longhand defaults are counterintuitive.
| Property | Default | What it does |
|---|---|---|
| flex-grow | 0 | Share of LEFTOVER space this item absorbs, as a ratio against its siblings. |
| flex-shrink | 1 | Share of OVERFLOW this item gives up. Set 0 to keep a sidebar from squashing. |
| flex-basis | auto | Starting size on the main axis before grow and shrink run. Beats width. |
| flex: 1 | 1 1 0% | Equal columns regardless of content, because the basis is zero. |
| flex: auto | 1 1 auto | Grows and shrinks, but starts from content size. Wider text gets a wider column. |
| flex: none | 0 0 auto | Fully rigid. The correct value for an icon or a fixed-width control. |
| flex: 0 0 280px | - | A sidebar that is exactly 280px and refuses to move. |
| align-self | auto | Overrides the container's align-items for this one item. |
| order | 0 | Visual reordering only. Tab order and screen readers still follow the DOM. |
| margin-left: auto | - | Eats all leftover space to the left, pushing this item and the rest right. |
| margin: auto | - | Centers a single item on both axes with no container properties at all. |
| min-width: 0 | - | The fix for a flex child that refuses to shrink and blows out the row. |
Gotcha: a flex item's automatic minimum size is its content size, not zero. That is why one long unbroken string or a wide table can push a row past its container. min-width: 0 on the flex child (and overflow: hidden if it holds text) is the fix, every time.
Grid container properties
| Property | Example | What it does |
|---|---|---|
| display | grid | Turns children into grid items. inline-grid keeps the box inline. |
| grid-template-columns | 200px 1fr 200px | Defines the explicit column tracks. Any length, percentage, fr, or function. |
| grid-template-rows | auto 1fr auto | Explicit row tracks. The classic header, body, footer shell. |
| grid-template-areas | "nav main" | Names regions with an ASCII picture. Rearranging is a one-line media query. |
| grid-auto-columns | minmax(120px, 1fr) | Size of columns created implicitly, beyond the template. |
| grid-auto-rows | minmax(80px, auto) | Size of implicit rows. The usual home for a minimum row height. |
| grid-auto-flow | row | column | dense | Direction auto-placement fills. dense backfills holes left by spans. |
| gap | 1rem 2rem | Row gap then column gap. Gaps are never added to the outside edges. |
| justify-items | stretch | start | center | end | How every item sits INSIDE its own cell, horizontally. |
| align-items | stretch | start | center | end | Same thing vertically. |
| justify-content | start | center | space-between | Where the WHOLE grid sits when the tracks are narrower than the container. |
| align-content | start | center | space-between | Same for the block axis, when rows do not fill the height. |
| place-items | center | align-items then justify-items. One value applies to both. |
| grid-template | rows / columns | Shorthand for rows, columns, and areas in one declaration. |
Grid item placement
Grid lines are numbered from 1, and negative numbers count back from the end. That is why 1 / -1 means "full width" no matter how many columns exist.
| Declaration | What it does |
|---|---|
| grid-column: 2 / 4 | Starts at line 2 and ends at line 4, covering two columns. |
| grid-column: span 2 | Two columns wide wherever auto-placement puts it. |
| grid-column: 1 / -1 | Full width of the explicit grid. The full-bleed row inside a card grid. |
| grid-row: 1 / span 3 | Starts on row line 1 and covers three rows. |
| grid-area: sidebar | Drops the item into the named region from grid-template-areas. |
| grid-area: 1 / 1 / 3 / 4 | Row-start, column-start, row-end, column-end in one shot. |
| grid-column: content-start / content-end | Named lines. Any area name automatically creates -start and -end lines. |
| justify-self: end | Pushes this one item to the right edge of its cell. |
| align-self: center | Vertically centers this one item in its cell. |
| place-self: center end | align-self then justify-self, in one declaration. |
| grid-template-columns: subgrid | On a grid item that is itself a grid: inherit the parent's tracks so nested content lines up. |
Gotcha: -1 refers to the last line of the EXPLICIT grid only. If items were auto-placed into implicit tracks, 1 / -1 stops short of them.
Every alignment property in one matrix
The single most useful rule: in Grid, justify-* is always the inline (column) axis and align-* is always the block (row) axis. In Flexbox they swap meaning when flex-direction is column. Every place-* shorthand takes align first, justify second.
| Property | In Flexbox | In Grid |
|---|---|---|
| justify-content | Items along the main axis. | The whole column track set inside the container. |
| align-content | Wrapped lines. No effect without wrapping. | The whole row track set inside the container. |
| justify-items | Ignored. | Every item inside its own cell, horizontally. |
| align-items | Every item on the cross axis. | Every item inside its own cell, vertically. |
| justify-self | Ignored. Use an auto margin. | One item inside its cell, horizontally. |
| align-self | One item on the cross axis. | One item inside its cell, vertically. |
| place-content | align-content justify-content. | align-content justify-content. |
| place-items | Only the align half applies. | align-items justify-items. place-items: center is the centering one-liner. |
| place-self | Only the align half applies. | align-self justify-self. |
Alignment values and what they actually do
| Value | Behavior |
|---|---|
| start / end | Writing-mode aware edges. Prefer these over left and right. |
| flex-start / flex-end | The Flexbox spellings. Identical to start and end in practice. |
| center | Centers along that axis. Leftover space splits evenly on both sides. |
| stretch | Fills the cross axis. Only works when the item has no fixed size on that axis. |
| baseline | Lines up the first line of text across items of different sizes. |
| space-between | First item flush left, last flush right, equal gaps between. |
| space-around | Equal space around each item, so edge gaps are half the inner gaps. |
| space-evenly | Every gap identical, including the two edges. Usually the one you meant. |
| safe center | Centers, but falls back to start rather than overflowing off-screen. |
Sizing: fr, minmax, auto-fit, and auto-fill
These are the track functions that make a grid responsive without a single media query.
| Value | What it means |
|---|---|
| 1fr | One share of the FREE space left after fixed tracks and gaps are subtracted. |
| 2fr 1fr | Two-to-one split of the free space, not of the container width. |
| minmax(200px, 1fr) | Never narrower than 200px, otherwise takes a share of the free space. |
| minmax(0, 1fr) | The overflow fix. Plain 1fr is minmax(auto, 1fr) and will not shrink below content. |
| repeat(3, 1fr) | Three equal columns. Expands to 1fr 1fr 1fr. |
| repeat(auto-fit, minmax(240px, 1fr)) | As many columns as fit; leftover EMPTY tracks collapse, so items stretch to fill. |
| repeat(auto-fill, minmax(240px, 1fr)) | Same, but empty tracks are KEPT, so three items in a five-column row stay narrow. |
| min-content | As narrow as the longest unbreakable word. |
| max-content | As wide as the content wants with no wrapping at all. |
| fit-content(300px) | max-content, clamped at 300px. A caption column that never gets silly. |
| auto | Content-sized, but stretches to absorb free space when no fr track exists. |
| clamp(16rem, 40%, 32rem) | Valid as a track size. Fluid width with a floor and a ceiling. |
| aspect-ratio: 16 / 9 | On the item, not the track. Keeps cells proportional as columns reflow. |
| [full-start] 1fr [content-start] | Named grid lines in brackets, for readable full-bleed layouts. |
Gotcha: auto-fit and auto-fill look identical until the row is not full. With four cards in a container wide enough for six, auto-fill leaves two empty slots and keeps the cards narrow; auto-fit collapses them and the four cards spread out. Pick deliberately.
Copy-paste layout recipes
Eight layouts that cover most of what a real page needs. All of them are modern CSS with no floats, no clearfix, and no positioning hacks.
Dead center, both axes
/* Grid: shortest version that exists */
.center { display: grid; place-items: center; min-block-size: 100dvh; }
/* Flexbox equivalent */
.center { display: flex; justify-content: center; align-items: center; }
/* No container properties at all: auto margins on the child */
.center > * { margin: auto; } Holy grail: header, sidebar, main, aside, footer
.shell {
display: grid;
min-block-size: 100dvh;
gap: 1rem;
grid-template-rows: auto 1fr auto;
grid-template-columns: 14rem minmax(0, 1fr) 12rem;
grid-template-areas:
"head head head"
"nav main aside"
"foot foot foot";
}
.shell > header { grid-area: head; }
.shell > nav { grid-area: nav; }
.shell > main { grid-area: main; }
.shell > aside { grid-area: aside; }
.shell > footer { grid-area: foot; }
@media (width < 60rem) {
.shell {
grid-template-columns: 1fr;
grid-template-areas: "head" "nav" "main" "aside" "foot";
}
} The minmax(0, 1fr) on the main column is not decoration. Without it, a wide table or a long code block inside main pushes the whole layout sideways.
Sidebar that collapses without a media query
.with-sidebar {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.with-sidebar > .side { flex: 1 1 16rem; } /* ideal width, may grow */
.with-sidebar > .body { flex: 999 1 60%; } /* hogs space until 60% is impossible */ The huge grow factor on the body means the sidebar only wraps when the body can no longer hold its 60% basis. Container queries do this more explicitly, but this version works in one declaration.
Responsive card grid, zero breakpoints
.cards {
display: grid;
gap: 1.5rem;
grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
}
.cards > .featured { grid-column: 1 / -1; } /* one full-width hero card */ The min(18rem, 100%) wrapper is what stops the grid overflowing on a 320px phone, where a hard 18rem minimum would be wider than the viewport.
Sticky footer that stays down on short pages
body {
min-block-size: 100dvh;
display: grid;
grid-template-rows: auto 1fr auto;
}
/* header, main, footer in source order - nothing else needed */ Nav bar with a group pushed to the right
.nav {
display: flex;
align-items: center;
gap: 1rem;
}
.nav > .logo { flex: none; }
.nav > .spacer { margin-inline-start: auto; } /* everything after this goes right */ Auto margins beat space-between here because they work with any number of items on either side.
Media object: fixed avatar, fluid text
.media {
display: grid;
grid-template-columns: 3rem minmax(0, 1fr);
gap: 0.75rem;
align-items: start;
}
/* Long words in the text column now wrap instead of overflowing */ Full-bleed row inside a constrained article
.article {
display: grid;
grid-template-columns:
[full-start] minmax(1rem, 1fr)
[content-start] min(70ch, 100%) [content-end]
minmax(1rem, 1fr) [full-end];
}
.article > * { grid-column: content; }
.article > .full-bleed { grid-column: full; } Because area names generate -start and -end lines, grid-column: content is shorthand for content-start / content-end.
Gotchas that cost people an afternoon
| Symptom | Cause and fix |
|---|---|
| A flex or grid child overflows its container | Automatic minimum size is content-based. Add min-width: 0 to the child, or use minmax(0, 1fr) for the track. |
| align-content does nothing | In Flexbox it only applies to multiple lines. Add flex-wrap: wrap. |
| Percentage heights collapse | A percentage height needs a resolved parent height. Use 1fr in a grid row or 100dvh at the root. |
| gap is added around the outside | It is not. If there is edge space, it is padding on the container or a stray margin on a child. |
| Images stretch in a flex row | Default align-items: stretch. Use align-self: start plus object-fit: contain. |
| Tab order does not match what I see | order and grid placement do not change the DOM. Reorder the markup instead. |
| A grid row is taller than its content | Items stretch by default. Set align-items: start on the container. |
| Nested card contents will not line up | Each card is its own grid. Use grid-template-rows: subgrid on the card so it borrows the parent's rows. |
Keep going
Writing these as utility classes instead of stylesheets? The Tailwind CSS cheatsheet maps every property here to its utility, including the grid track and alignment utilities that are easy to guess wrong.
Still choosing an approach rather than writing one, the CSS and styling tool guide covers frameworks, component libraries, and design tokens, and Tailwind vs CSS Modules settles the authoring question with a verdict.
Specs worth bookmarking: MDN on Flexbox and MDN on Grid. More quick references live in the cheatsheet index.