設計基礎

Layout System

Build responsive layout systems with containers, columns, gutters, breakpoints, container sizes, spacing tokens, CSS Grid, and Flexbox.

Overview

CSS layout is built from native primitives. CSS Grid arranges content across two axes. Flexbox distributes content along one axis. A layout system turns those primitives into repeatable decisions: page containers, column counts, gutters, outer margins, content regions, and responsive rules.

In Master CSS, those decisions stay close to the markup and share the same foundations as the rest of the interface:

  • Breakpoints define when page-level layout changes.
  • Containers define component-level changes and shared width caps.
  • Spacing defines gutters, margins, and section rhythm.
  • Sizing defines wrappers, fixed regions, fluid regions, and constraints.
  • Grid and Flex utilities express the actual composition.

Drag the edges to see the surface move from a compact stacked layout to an 8-column composition.

/examples/layout-system
<main class="w:full max-w:3xl mx:auto p:md">    <section class="grid-cols:4 grid-cols:8@2xs gap:sm gap:md@2xs">        <header class="grid-col-span:4 grid-col-span:8@2xs">...</header>        <article class="grid-col-span:4 grid-col-span:4@2xs">...</article>        <article class="grid-col-span:4 grid-col-span:5@2xs">...</article>        <aside class="grid-col-span:4 grid-col-span:3@2xs">...</aside>    </section></main>
Generated CSS
@layer theme {    :root {        --container-3xl: 48rem;        --spacing-md: 1rem;        --spacing-sm: .75rem    }}@layer utilities {    .gap\:sm {        gap: var(--spacing-sm)    }    .grid-col-span\:4 {        grid-column: span 4/span 4    }    .mx\:auto {        margin-inline: auto    }    .p\:md {        padding: var(--spacing-md)    }    .grid-cols\:4 {        display: grid;        grid-template-columns: repeat(4, minmax(0, 1fr))    }    .max-w\:3xl {        max-width: var(--container-3xl)    }    .w\:full {        width: 100%    }    @media (width>=37.5rem) {        .gap\:md\@2xs {            gap: var(--spacing-md)        }    }    @media (width>=37.5rem) {        .grid-col-span\:3\@2xs {            grid-column: span 3/span 3        }    }    @media (width>=37.5rem) {        .grid-col-span\:4\@2xs {            grid-column: span 4/span 4        }    }    @media (width>=37.5rem) {        .grid-col-span\:5\@2xs {            grid-column: span 5/span 5        }    }    @media (width>=37.5rem) {        .grid-col-span\:8\@2xs {            grid-column: span 8/span 8        }    }    @media (width>=37.5rem) {        .grid-cols\:8\@2xs {            display: grid;            grid-template-columns: repeat(8, minmax(0, 1fr))        }    }}

Define the system

A layout system should answer five questions before individual components start styling themselves.

DecisionMaster CSS foundationTypical utilities
Canvas widthContainer sizes and sizingw:full, max-w:7xl, mx:auto
Outer marginSpacingpx:md, px:xl@md
ColumnsGridgrid-cols:4, grid-cols:12@md
GuttersSpacinggap:md, gap-x:lg, gap-y:xl
Region behaviorGrid, Flex, and queriesgrid-col-span:8@md, flex-row@2xs, @container(md)

These decisions are separate on purpose. A page can keep the same maximum width while changing column count. A component can keep the same columns while increasing its gutter. A reusable card can respond to its container instead of the viewport.


Start with a page container

The container defines the live area for the layout. Use w:full so the wrapper can shrink, max-w:* so it aligns with the container size scale, mx:auto to center it, and horizontal padding from the spacing scale for the outer margin.

<main class="w:full max-w:7xl mx:auto px:md px:xl@md">    ...</main>
Generated CSS
@layer theme {    :root {        --container-7xl: 80rem;        --spacing-md: 1rem;        --spacing-xl: 2rem    }}@layer utilities {    .mx\:auto {        margin-inline: auto    }    .px\:md {        padding-inline: var(--spacing-md)    }    .max-w\:7xl {        max-width: var(--container-7xl)    }    .w\:full {        width: 100%    }    @media (width>=64rem) {        .px\:xl\@md {            padding-inline: var(--spacing-xl)        }    }}

Use the wrapper for page-level alignment. Use inner grids for content relationships. This keeps outer margins, columns, and card padding from competing with each other.

Use container-* values for layout wrappers that should follow the shared container scale. Use local x, ch, or clamp() values when the maximum width is based on content measure instead of the responsive scale.


Build column grids

Column grids are most useful when several regions need to align across a page. A 4, 8, or 12 column structure is a practical default because it supports halves, thirds, quarters, and asymmetrical content regions without forcing narrow columns everywhere.

Use grid-cols:<count> to create equal-width columns. It also sets display:grid, so a separate grid class is not required for this pattern.

<section class="grid-cols:4 grid-cols:8@2xs grid-cols:12@md gap:lg">    <article class="grid-col-span:4 grid-col-span:5@2xs grid-col-span:8@md">...</article>    <aside class="grid-col-span:4 grid-col-span:3@2xs grid-col-span:4@md">...</aside></section>
Generated CSS
@layer theme {    :root {        --spacing-lg: 1.5rem    }}@layer utilities {    .gap\:lg {        gap: var(--spacing-lg)    }    .grid-col-span\:4 {        grid-column: span 4/span 4    }    .grid-cols\:4 {        display: grid;        grid-template-columns: repeat(4, minmax(0, 1fr))    }    @media (width>=37.5rem) {        .grid-col-span\:3\@2xs {            grid-column: span 3/span 3        }    }    @media (width>=37.5rem) {        .grid-col-span\:5\@2xs {            grid-column: span 5/span 5        }    }    @media (width>=37.5rem) {        .grid-cols\:8\@2xs {            display: grid;            grid-template-columns: repeat(8, minmax(0, 1fr))        }    }    @media (width>=64rem) {        .grid-col-span\:4\@md {            grid-column: span 4/span 4        }    }    @media (width>=64rem) {        .grid-col-span\:8\@md {            grid-column: span 8/span 8        }    }    @media (width>=64rem) {        .grid-cols\:12\@md {            display: grid;            grid-template-columns: repeat(12, minmax(0, 1fr))        }    }}

Start with the smallest layout that works, then add breakpoint-specific column counts and spans only where the relationship changes. This keeps the markup readable and avoids maintaining unused breakpoints.


Use gutters from spacing

Gutters are the spaces between columns and rows. They should come from spacing tokens so grid rhythm matches component padding, form spacing, and section separation.

Use gap:* when both axes share the same rhythm. Use gap-x:* and gap-y:* when horizontal gutters and vertical stacking need different values.

<section class="grid-cols:1 grid-cols:3@md gap:md gap-x:lg@md gap-y:xl@md">    ...</section>
Generated CSS
@layer theme {    :root {        --spacing-md: 1rem;        --spacing-lg: 1.5rem;        --spacing-xl: 2rem    }}@layer utilities {    .gap\:md {        gap: var(--spacing-md)    }    .grid-cols\:1 {        display: grid;        grid-template-columns: repeat(1, minmax(0, 1fr))    }    @media (width>=64rem) {        .gap-x\:lg\@md {            column-gap: var(--spacing-lg)        }    }    @media (width>=64rem) {        .gap-y\:xl\@md {            row-gap: var(--spacing-xl)        }    }    @media (width>=64rem) {        .grid-cols\:3\@md {            display: grid;            grid-template-columns: repeat(3, minmax(0, 1fr))        }    }}

Do not use arbitrary gutters as the default language of a design system.

<section class="grid-cols:3 gap:1.438rem">...</section>

Use a shared spacing token, or add one when the product has a named gutter.

<section class="grid-cols:3 gap:lg">...</section>

Place regions intentionally

Grid placement should describe the region's role in the layout, not the visual size of an individual card. Use spans for common regions and line placement when the design needs a specific start or end.

GoalUtility
Span columnsgrid-col-span:<count>
Place between linesgrid-col:<start>/<end>
Set explicit startgrid-col-start:<line>
Set explicit endgrid-col-end:<line>
Span rowsgrid-row-span:<count>
Assign a named areagrid-area:<name>
<section class="grid-cols:12 gap:lg">    <header class="grid-col-span:12">...</header>    <article class="grid-col:3/11@md grid-row-span:2@md">...</article></section>
Generated CSS
@layer theme {    :root {        --spacing-lg: 1.5rem    }}@layer utilities {    .gap\:lg {        gap: var(--spacing-lg)    }    .grid-col-span\:12 {        grid-column: span 12/span 12    }    .grid-cols\:12 {        display: grid;        grid-template-columns: repeat(12, minmax(0, 1fr))    }    @media (width>=64rem) {        .grid-col\:3\/11\@md {            grid-column: 3/11        }    }    @media (width>=64rem) {        .grid-row-span\:2\@md {            grid-row: span 2/span 2        }    }}

For regular content pages, spans are usually easier to maintain than exact line numbers. Use exact lines for editorial layouts, dashboards, and app shells where the page has a stable structural grid.


Choose CSS Grid or Flexbox

Use Grid when the parent owns the columns, rows, or area placement. Use Flexbox when the content owns its natural size and the parent only needs to distribute, align, wrap, or switch direction.

Use Grid forUse Flexbox for
Page regionsToolbars
Card galleriesMenus and tabs
Editorial spansMedia objects
DashboardsInline controls
Two-axis alignmentOne-axis distribution
Flex media object
Quarterly planning

The image keeps a measured basis while the content expands, shrinks, and wraps around the available inline space.

RoadmapReady
<article class="container w:full flex flex-col flex-row@container(2xs) gap:md">    <img class="w:full w:36x@container(2xs) flex:0@container(2xs)" alt="" />    <div class="min-w:0 flex:1">...</div></article>
Generated CSS
@layer theme {    :root {        --spacing-md: 1rem    }}@layer utilities {    .container {        container-type: inline-size    }    .flex {        display: flex    }    .flex-col {        flex-direction: column    }    .flex\:1 {        flex: 1    }    .gap\:md {        gap: var(--spacing-md)    }    .min-w\:0 {        min-width: 0    }    .w\:full {        width: 100%    }    @container (width>=18rem) {        .flex-row\@container\(2xs\) {            flex-direction: row        }    }    @container (width>=18rem) {        .flex\:0\@container\(2xs\) {            flex: 0        }    }    @container (width>=18rem) {        .w\:36x\@container\(2xs\) {            width: 9rem        }    }}

min-w:0 is important for flexible text regions. It allows long content to shrink inside a flex or grid item instead of forcing the whole layout wider than the container.


Use container queries for components

Viewport breakpoints are a good fit for page-level layout. Container queries are a better fit for reusable components because the same component can appear in a narrow sidebar, a modal, or a wide content column.

Add container to the component boundary, then use @container(<size>) on descendants.

<section class="container">    <article class="grid-cols:1 grid-cols:2@container(2xs) gap:md gap:lg@container(2xs)">        ...    </article></section>
Generated CSS
@layer theme {    :root {        --spacing-md: 1rem;        --spacing-lg: 1.5rem    }}@layer utilities {    .container {        container-type: inline-size    }    .gap\:md {        gap: var(--spacing-md)    }    .grid-cols\:1 {        display: grid;        grid-template-columns: repeat(1, minmax(0, 1fr))    }    @container (width>=18rem) {        .gap\:lg\@container\(2xs\) {            gap: var(--spacing-lg)        }    }    @container (width>=18rem) {        .grid-cols\:2\@container\(2xs\) {            display: grid;            grid-template-columns: repeat(2, minmax(0, 1fr))        }    }}

Use viewport breakpoints for global decisions such as page shell, navigation mode, and maximum canvas width. Use container queries for cards, forms, media objects, and reusable panels.


Add explicit tracks only when needed

grid-cols:* is best for equal columns. Use grid-template-columns:* when the layout needs fixed, intrinsic, or mixed tracks.

<section class="grid grid-template-columns:18x|minmax(0,1fr) gap:md">    <nav>...</nav>    <main class="min-w:0">...</main></section>
Generated CSS
@layer theme {    :root {        --spacing-md: 1rem    }}@layer utilities {    .grid {        display: grid    }    .gap\:md {        gap: var(--spacing-md)    }    .grid-template-columns\:18x\|minmax\(0\,1fr\) {        grid-template-columns: 4.5rem minmax(0, 1fr)    }    .min-w\:0 {        min-width: 0    }}

Use explicit tracks for app shells, data views, split panes, and content that has a real fixed region. For ordinary responsive content, equal columns plus spans are usually more flexible.


Common patterns

Page shell

<main class="w:full max-w:7xl mx:auto px:lg px:2xl@lg">    ...</main>

Content with sidebar

<section class="grid-cols:1 grid-cols:12@md gap:xl">    <article class="grid-col-span:8@md">...</article>    <aside class="grid-col-span:4@md">...</aside></section>
<section class="grid-cols:1 grid-cols:2@2xs grid-cols:3@md gap:lg">    ...</section>

Wrapping toolbar

<div class="flex flex-wrap items-center gap:sm">    ...</div>

Media object

<article class="flex flex-col flex-row@2xs gap:md">    <img class="w:full w:32x@2xs flex:0@2xs" alt="" />    <div class="min-w:0">...</div></article>

Common mistakes

Changing columns without changing spans

The grid becomes 12 columns, but each region still spans the mobile 4-column structure.

<section class="grid-cols:4 grid-cols:12@md gap:lg">    <article class="grid-col-span:4">...</article>    <aside class="grid-col-span:4">...</aside></section>

Update the spans at the same breakpoint as the column change.

<section class="grid-cols:4 grid-cols:12@md gap:lg">    <article class="grid-col-span:4 grid-col-span:8@md">...</article>    <aside class="grid-col-span:4 grid-col-span:4@md">...</aside></section>

Using viewport breakpoints inside every component

The card changes based on the page viewport, even when it appears in a narrow parent.

<article class="grid-cols:1 grid-cols:2@md gap:lg">...</article>

Let the card respond to the space it actually receives.

<section class="container">    <article class="grid-cols:1 grid-cols:2@container(2xs) gap:lg">...</article></section>

Treating CSS Grid and Flexbox as interchangeable

Use Grid when children need to share a column system. Use Flexbox when children need to wrap, align, or distribute along one axis. This distinction keeps page layout predictable and keeps components simple.


Summary

Build layout systems from stable foundations:

  • Use breakpoint tokens for viewport decisions and container tokens for wrapper decisions.
  • Use spacing tokens for gutters, outer margins, and section rhythm.
  • Use grid-cols:* and placement utilities for page regions and galleries.
  • Use flex, direction, wrapping, and flex:* for one-axis component layout.
  • Use container queries when a reusable component should adapt to its own available width.

The result is a layout language that is explicit in markup, token-aligned with the design system, and flexible enough for both page-level grids and component-level composition.


  • Master UI


© 2026 Aoyue Design LLC.MIT License
Trademark Policy