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-directionrow (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. Prefer gap over child margins for consistent gutters.

Cross axis controls

  • align-items — aligns items on the cross axis inside the container (stretch is default and fills height in a row layout).
  • align-self — overrides alignment for a single item.
  • align-content — distributes multiple wrapped lines when flex-wrap: wrap is 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 anythingdisplay: flex; justify-content: center; align-items: center; on the parent.
  • Horizontal nav — row flex with gap; push one item right with margin-left: auto on that item.
  • Sticky footer — column flex on body with min-height: 100vh; set margin-top: auto on footer so 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.

ScenarioBest toolWhy
Navbar with logo + linksFlexboxSingle horizontal flow; margin-left: auto pushes actions right
Responsive card galleryGridauto-fit minmax() handles column count without breakpoint soup
Vertically centering a modalFlexbox or GridBoth work; Flexbox is fewer lines for simple centering
Full page with sidebarGridNamed areas make header/sidebar/main/footer explicit
Equal-width buttons in a rowFlexboxflex: 1 on each button divides space evenly
Calendar or data tableGridFixed 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() and clamp() — 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. Set container-type: inline-size on 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: auto prevents shrinking below content width. Set min-width: 0 (or overflow: hidden) on flex children that must truncate or scroll.
  • Grid blowout from long words — unbroken strings can expand tracks. Use minmax(0, 1fr) or overflow-wrap: anywhere on 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 width and height attributes (or aspect-ratio) so the browser reserves space before images load.
  • Ignoring source order for keyboard users — visual reordering with order or 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(...)) and gap eliminate 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