Guide
CSS Flexbox and Grid layout explained
Before Flexbox and Grid, web developers fought floats, clearfix hacks, and table-based layouts to place elements on screen. Today, two CSS modules handle almost every layout problem: Flexbox arranges items along a single axis (a row or a column), while Grid places items in both rows and columns at once. Together they replaced an entire generation of fragile hacks. This guide explains how each works, when to reach for which, responsive patterns that survive real viewports, and the mistakes that still trip up production sites — including how layout choices affect Core Web Vitals and accessibility.
Why layout still matters
Layout is not decoration — it is structure. A navigation bar that wraps awkwardly on mobile, a card grid that leaves a ragged last row, or a form whose labels misalign on Safari all signal low craft and increase bounce rates. Search engines also reward pages that render predictably across devices; layout shifts (unexpected movement after paint) directly hurt Cumulative Layout Shift (CLS).
Modern CSS layout is declarative: you describe relationships between items and let the browser compute positions. That is faster to maintain than pixel-pushing every breakpoint, and it pairs naturally with component frameworks written in TypeScript or rendered via SSR and hydration.
Flexbox: one-dimensional layout
Apply display: flex to a flex container. Its
direct children become flex items. Flexbox distributes
space along one main axis (horizontal by default) and
aligns items on the perpendicular cross axis.
Main axis controls
flex-direction—row(default),column, or reversed variants. This defines which axis is "main."justify-content— distributes items along the main axis:flex-start,center,space-between,space-around,space-evenly.gap— fixed spacing between items without margin hacks. Prefergapover child margins for consistent gutters.
Cross axis controls
align-items— aligns items on the cross axis inside the container (stretchis default and fills height in a row layout).align-self— overrides alignment for a single item.align-content— distributes multiple wrapped lines whenflex-wrap: wrapis set.
Flex item sizing
The shorthand flex: 1 expands to flex-grow: 1; flex-shrink: 1; flex-basis: 0%,
telling an item to consume leftover space equally with siblings. Use
flex: 0 0 auto (or flex: none) when an item must
keep its intrinsic width — common for icons and buttons in toolbars.
flex-basis sets the starting size before grow/shrink kicks in;
think of it as a preferred width or height.
Common Flexbox patterns:
- Center anything —
display: flex; justify-content: center; align-items: center;on the parent. - Horizontal nav — row flex with
gap; push one item right withmargin-left: autoon that item. - Sticky footer — column flex on
bodywithmin-height: 100vh; setmargin-top: autoonfooterso it pins to the bottom when content is short. - Equal-height cards in a row — flex row with
align-items: stretch(default); inner card content grows to match tallest sibling.
Grid: two-dimensional layout
display: grid defines a grid container with
explicit rows and columns. Items are placed into grid cells
by auto-placement rules or by naming areas. Grid excels at page-level
structure: headers, sidebars, content wells, and card galleries with
predictable alignment in both dimensions.
Defining tracks
grid-template-columns and grid-template-rows accept
lengths, percentages, and the powerful fr (fraction) unit.
1fr 1fr 1fr splits available space into three equal columns.
minmax(200px, 1fr) prevents columns from shrinking below 200px
while still growing to fill space — the workhorse of responsive card grids.
The pattern repeat(auto-fit, minmax(280px, 1fr)) creates as
many columns as fit at least 280px wide, wrapping automatically as the
viewport narrows. No media query required for the column count — the grid
reflows itself.
Placement and named areas
grid-column: 1 / 3 spans from line 1 to line 3 (two columns).
For whole-page layouts, grid-template-areas is readable:
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 240px 1fr;
Assign each child grid-area: header, etc. Rearranging the
template string at a breakpoint reshuffles the entire page without touching
HTML order — useful for responsive sidebars that collapse below content on
mobile.
Alignment in Grid
justify-items/align-items— align content inside each cell.justify-content/align-content— align the whole grid when tracks do not fill the container.place-items: center— shorthand to center both axes inside cells.
Flexbox vs Grid: a decision framework
The rule of thumb is simple: if you are laying out items in a line (toolbar, form row, vertical stack), use Flexbox. If you need rows and columns simultaneously (dashboard, photo gallery, magazine layout), use Grid.
| Scenario | Best tool | Why |
|---|---|---|
| Navbar with logo + links | Flexbox | Single horizontal flow; margin-left: auto pushes actions right |
| Responsive card gallery | Grid | auto-fit minmax() handles column count without breakpoint soup |
| Vertically centering a modal | Flexbox or Grid | Both work; Flexbox is fewer lines for simple centering |
| Full page with sidebar | Grid | Named areas make header/sidebar/main/footer explicit |
| Equal-width buttons in a row | Flexbox | flex: 1 on each button divides space evenly |
| Calendar or data table | Grid | Fixed column tracks align cells across rows |
They compose well: a Grid cell can contain a Flexbox toolbar; a Flexbox row can hold Grid card grids. Nesting is normal — just avoid deep stacks of both without a reason, because each formatting context adds mental overhead.
Responsive layout without breakpoint fatigue
Media queries keyed to device widths (@media (max-width: 768px))
remain useful for typography and navigation mode changes, but layout
reflow often needs fewer breakpoints when you use intrinsic sizing:
- Grid
auto-fit/auto-fill— column count follows container width. - Flexbox
flex-wrap: wrap— items wrap to new lines when space runs out. min()andclamp()— fluid spacing and font sizes without discrete steps.- Container queries —
@container (min-width: 400px)responds to the parent component's width, not the viewport. A sidebar card can switch from stacked to side-by-side when its container is wide enough, even inside a narrow page column. Setcontainer-type: inline-sizeon the parent.
Reserve viewport media queries for global concerns (hiding a desktop nav, changing root font size). Let components adapt locally with Grid, Flexbox, and container queries.
Subgrid and modern extras
Subgrid (display: subgrid on a nested grid)
lets child grids inherit parent track sizing — useful when card internals
must align across a row (e.g., titles, bodies, and footers line up even when
card heights differ). Browser support is solid in current Chrome, Firefox,
and Safari.
gap works in both Flexbox and Grid (unified since Flexbox gap
shipped everywhere). Prefer it over negative-margin hacks on wrappers.
aspect-ratio pairs with Grid to keep thumbnails square while
columns reflow.
Common pitfalls
- Flex items refusing to shrink — default
min-width: autoprevents shrinking below content width. Setmin-width: 0(oroverflow: hidden) on flex children that must truncate or scroll. - Grid blowout from long words — unbroken strings can expand tracks. Use
minmax(0, 1fr)oroverflow-wrap: anywhereon grid children. - Using floats for layout in 2026 — floats are for text wrapping around images, not page structure. Flexbox and Grid replaced them.
- Absolute positioning for flow — taken-out-of-flow elements do not push siblings; reserve absolute for overlays, badges, and tooltips.
- Layout shift from unsized images — always set
widthandheightattributes (oraspect-ratio) so the browser reserves space before images load. - Ignoring source order for keyboard users — visual reordering with
orderor grid placement does not change tab order. Keep focusable elements in logical DOM order.
Key takeaways
- Flexbox lays out along one axis — ideal for nav bars, stacks, and distributing leftover space with
flex-grow. - Grid controls rows and columns together — ideal for page shells, card galleries, and data-dense UIs.
repeat(auto-fit, minmax(...))andgapeliminate most manual breakpoint math for grids.- Container queries let components respond to their parent, not just the viewport.
- Layout choices affect CLS and keyboard navigation — size media, respect DOM order, and test on real devices.
Related reading
- TypeScript fundamentals — typing component props that drive layout variants
- SSR vs CSR vs SSG vs ISR — how rendering strategy interacts with CSS delivery
- Core Web Vitals explained — preventing layout shift and interaction delay
- Web accessibility (a11y) explained — focus order, contrast, and semantic structure alongside layout