Skip to main content

Learning Track

Filter content by your role to see the most relevant sections

Grid System Foundations

Organize layouts with predictable flow and alignment: column structures on tokenized containers and breakpoints, gutters from the spacing scale, and the fluid-grid habits that keep dense layouts readable and reflowable.

Darian Rosebrook
Staff Design Technologist, Design Systems Architect
Expertise:Design Systems, Layout, Grids

Why This Matters

A grid is a promise about alignment: everything on this page shares an invisible structure, so the eye can scan along lines it never sees. Break the promise—columns that almost line up, gutters that vary by a few pixels—and the cost is real: scanning gets harder, density gets noisy, and users cannot tell deliberate grouping from accident.

Grids are also where design tools and code drift fastest, because everyone agrees grids matter and everyone implements them alone. The system answer is the same as every other foundation: columns counted in a shared convention, gutters and margins from the spacing scale, adaptation at the tokenized breakpoints. The grid is not a scaffold you redraw per screen; it is a rhythm you reference.

This page covers grid as composed here—there is no separate grid token file, and that is the lesson: a good grid is composed from the layout, dimension, and spacing tokens you have already met.

Core Concepts: Columns, Gutters, Margins, Behavior

A Column Convention, Not a Column File

The system's grid is a 12-column fluid grid inside the tokenized containers, dropping to simpler counts on small screens:

// Column convention (composed, not stored)
//                columns   container cap            at breakpoint
phones           4         fluid, full-bleed        < md 768
tablets          8         layout.container.sm 640  >= md 768
desktop          12        layout.container.lg 1024 >= lg 1024
wide desktop     12        layout.container.xl 1280 >= xl 1440

Twelve divides by 2, 3, 4, and 6, which is why it survived every framework war: halves, thirds, quarters, and sixths all land on column boundaries. The smaller counts exist because a 12-column grid on a phone produces slivers—columns narrower than their gutters are structure with no meaning.

Gutters and Margins Come from the Spacing Scale

Gutters are spacing decisions and use the scale from the spacing foundation: spacing.size.04 (8px) on phones, size.06 (16px) from tablet up, stepping to size.07 (24px) for wide desktops. Page margins use the same steps (16px mobile, 32px from lg—the same two-value rule the layout foundation set for page gutters). The consequence worth internalizing: the grid's vertical rhythm and the component's internal rhythm are the same scale, so cards snap to gutters without translation arithmetic.

Fluid Inside, Discrete At the Edges

Columns are fluid—1fr all the way to their container cap—while the structure changes only at the six tokenized dimension.breakpoint steps (640, 768, 1024, 1440, 1536, 1920). This is the layout foundation's rule applied to grids: continuous behavior between a small number of named adaptation points. A grid that re-specifies itself at every breakpoint is four layouts wearing one class name.

Subgrids and the Alignment Promise

Nested content keeps the promise with CSS subgrid: a card whose internal rows align to the page grid's rows, so a row of cards with different title lengths still aligns their actions. Where subgrid support is unavailable, the fallback is the old craft rule—equal-height flex items with pinned footers—because the alignment promise predates the property that makes it free.

When Not to Grid

Grids are for regular structure—repeated items, tabular relationships, aligned regions. Editorial layouts, reading flows, and exploratory spaces want the container and flow discipline from the layout foundation instead. Forcing a 12-column grid onto an essay produces margins with no job.

System Roles: Where Grid Decisions Land

Design Impact

Designers own the column spans and the density judgment: what spans 12 vs 8 vs 6, when a 2-up becomes a 3-up, and how much gutter a given content weight needs. The Figma counterpart is layout grids with the same column counts and spacing-scale gutters, applied to frames rather than drawn per screen.

Engineering Impact

Engineers own the implementation contract: CSS Grid with repeat(12, 1fr) and spacing-scale gaps, spans via utility or scoped classes, subgrid where alignment demands it—and no resurrected float or percentage-padding skeletons from earlier CSS eras.

Accessibility Impact

Accessibility owns reading order and reflow: DOM order equals visual order (grids make re-ordering seductive and screen readers follow the DOM), and the 320px single-column reflow guarantee from the layout foundation applies to every grid layout. A grid that only works at 12 columns is a zoom-mode failure.

Design & Code Interplay

In the design tool, the grid shows up as frame-level layout grids—4/8/12 columns by breakpoint, gutters typed from the spacing scale—and components drawn to snap. The comp-review vocabulary is span-based: "this is an 8-span with a 4-span aside" is a sentence both sides can implement, because spans are arithmetic on shared structure.

The design-side trap to name in review: elements that are almost on the grid—offset by half a gutter, sized to the wrong fraction. Either it is on the structure or it is floating decoration; "close" is the drift that kills the promise.

Applied Example: A Search Results Page

Compose a grid-heavy page—search results with filters—entirely from shared structure:

  1. Structural grid: at lg, a 12-column grid with the filters as a 3-span and results as a 9-span, 16px gutter. Below md, filters collapse into a disclosure above a 4-column result grid.
  2. Card grid inside the 9-span: auto-fill with minmax(280px, 1fr) so card count follows available width without new breakpoints—the card grid adapts continuously inside the discrete structural one.
  3. Alignment via subgrid: three-row cards (title, snippet, metadata) share rows, so actions align regardless of snippet length.
  4. Reading order: filters come after the results in the DOM and are placed visually with grid placement—screen reader users hit results first, which is the correct priority, and the visual order is a styling fact, not a DOM fact.
  5. Reflow proof: at 320px everything is one column, filters collapsed, cards full-width—the same guarantee every layout in the system carries.

Zero new values; one convention; every number already named in a token file. That is what composing a foundation means.

Constraints & Trade-offs

  • Fixed column counts vs fluid spans: 4/8/12 is coarse; fully fluid spans adapt more but stop being a shared language—no one can say what spans what. The system takes discrete structure with fluid inside.
  • Shared gutters vs per-context gutters: one gutter scale keeps card gaps and page margins rhyming; teams that want airier section spacing should step the spacing token, not fork the grid.
  • Grid placement vs DOM order: visual re-ordering is free with grid and costs accessibility if the DOM disagrees; the rule (DOM = reading order) makes the flexibility safe.
  • Subgrid vs fallbacks: subgrid buys perfect cross-card alignment where supported; the flex fallback is more code for less guarantee. Choose per how much the alignment promise matters to the pattern.

Common Pitfalls & Failure Modes

1. Off-grid offsets

/* Bad: Half-gutter drift breaks the alignment promise */
.card { margin-left: 8px; }
/* Good: On the structure or in a named gutter */

2. Breakpoint-per-layout grids

/* Bad: A new grid definition at every query */
@media (width >= 900px) { .grid { grid-template-columns: repeat(10, 1fr); } }
/* Good: The tokenized steps only; fluid inside */

3. Sliver columns on small screens

/* Bad: 12 columns on a phone */
.phoneGrid { grid-template-columns: repeat(12, 1fr); }
/* Good: Column count steps down with the viewport */
.phoneGrid { grid-template-columns: repeat(4, 1fr); }

4. DOM order diverging from visual order

/* Bad: Pretty for eyes, scrambled for screen readers */
.results { order: 2; } .filters { order: 1; }
/* Good: DOM carries reading order; grid placement is styling */

Grid System Health Metrics

Signal 1: Token composition

  • Healthy: grid declarations compose only tokenized containers, breakpoints, and spacing-scale gutters; the 4/8/12 convention holds everywhere a grid exists.
  • Warning: one area runs its own column count (the "10-column marketing grid")—structure that no longer shares language with the rest of the product.
  • Critical: grids are bespoke per screen; span vocabulary is dead and every layout is a negotiation.

Signal 2: Continuity between adaptation points

  • Healthy: fluid columns between the tokenized breakpoints; resizing between sizes never jumps structure.
  • Warning: discrete re-specification at each query—each size is a layout nobody tests between its edges.
  • Critical: viewport sweeps break layouts visibly; the grid exists only at the designed widths.

Signal 3: Order fidelity

  • Healthy: DOM order equals reading order everywhere; placement achieves every visual arrangement.
  • Warning: one screen re-ordered its DOM for a sketch—the divergence is invisible until an AT user or the e2e reading check finds it.
  • Critical: visual and DOM order habitually disagree; two audiences use different products.

Migration Strategy: Retiring Legacy Scaffolds

  1. Inventory the scaffolds: float clearfixes, percentage paddings posing as gutters, fixed-width wrappers, per-framework grid classes— each tagged with the screens it holds up.
  2. Re-express in CSS Grid beside the original: same breakpoints, same spans expressed as span N; screenshot-diff at the tokenized steps—legacy scaffolds were approximating this declaration all along.
  3. Adopt auto-fill/minmax for repeated items: card grids lose their per-breakpoint queries entirely; adaptation becomes continuous inside discrete structure.
  4. Add subgrid where alignment matters: equal-height cards with aligned actions migrate from flex hacks to three-row subgrid; delete the hacks with the migration.
  5. Verify order and reflow per screen: reading order preserved, 320px single-column—then delete the scaffold behind the flag that proved it.

Real-World Case Studies

Case 1: The float graveyard

A legacy catalog page carried 40 float-clearfix scaffolds that broke whenever content heights varied. The Grid re-expression produced the same layout at the same breakpoints in a quarter of the CSS, and the varying-height breakage class simply ceased to exist— not fixed, unreachable.

Case 2: The six-query card grid

Card counts were re-specified at six breakpoints; every new viewport width re-argued them. auto-fill + minmax(280px, 1fr) replaced all six queries with continuous adaptation—the card count now follows the available width without asking anyone.

Case 3: The subgrid alignment debt

Action buttons inside product cards were pinned with per-card flex gymnastics that drifted every quarter. Three-row subgrid pinned them structurally; the gymnastics were deleted, and the alignment promise now costs zero lines per new card.

Verification Checklist

Additional Resources

  • Layout — containers, measure, and the breakpoint scale this grid composes (/blueprints/foundations/layout)
  • Spacing & Sizing — where the gutters come from (/blueprints/foundations/spacing)
  • CSS Grid Layout (MDN) — the platform features this system uses (https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout)
  • The sources — ui/designTokens/core/layout.tokens.json, ui/designTokens/core/dimension.tokens.json, ui/designTokens/core/spacing.tokens.json

Related Concepts

Reflection Questions