Layout Grid Skill
This skill covers design grid systems for page layout—fluid, resolution-independent grids that scale proportionally across all viewport sizes without fixed breakpoints.
Philosophy
Layout grids should:
-
Scale fluidly - No fixed pixel values; use relative units throughout
-
Work at any resolution - Single grid system, not breakpoint-specific grids
-
Maintain proportions - Gutters and margins scale with content
-
Support composition - Enable both rigid alignment and flexible content areas
Fluid Grid Token System
Define grid properties as fluid custom properties that scale between viewport bounds.
Core Grid Tokens
@layer tokens { :root { /* ==================== GRID FOUNDATION ==================== */
/* Viewport bounds for fluid calculations */
--grid-min-width: 20rem; /* 320px - mobile */
--grid-max-width: 90rem; /* 1440px - large desktop */
/* Content max-width (readable area) */
--content-max-width: 75rem; /* 1200px */
/* ==================== FLUID GUTTER ==================== */
/* Gutter scales from 1rem to 2rem based on viewport */
--grid-gutter: clamp(1rem, 0.5rem + 2vw, 2rem);
/* ==================== FLUID MARGIN ==================== */
/* Page margin scales from 1rem to 4rem */
--grid-margin: clamp(1rem, -0.5rem + 6vw, 4rem);
/* ==================== COLUMN SYSTEM ==================== */
--grid-columns: 12;
/* Single column width (fluid) */
--grid-column-width: calc(
(100% - (var(--grid-columns) - 1) * var(--grid-gutter)) / var(--grid-columns)
);
} }
The Fluid Scaling Formula
Fluid values use clamp() with a calculated preferred value:
clamp(min, preferred, max) preferred = min + (max - min) × viewport-factor viewport-factor = (100vw - min-viewport) / (max-viewport - min-viewport)
Simplified pattern:
/* Scale from 1rem (320px) to 2rem (1440px) */ --value: clamp(1rem, calc(0.5rem + 1.5vw), 2rem);
Page Layout Container
The Fluid Container
@layer layout { /* Main content container */ body > * { --_container-width: min( var(--content-max-width), 100% - var(--grid-margin) * 2 );
width: var(--_container-width);
margin-inline: auto;
}
/* Full-bleed sections */ [data-layout="full"] { width: 100%; padding-inline: var(--grid-margin); }
/* Wide sections (larger than content, not full) */ [data-layout="wide"] { --_container-width: min( var(--grid-max-width), 100% - var(--grid-margin) * 2 ); } }
Named Grid Areas for Page Layout
@layer layout { body { display: grid; grid-template-columns: [full-start] var(--grid-margin) [wide-start] minmax(0, 1fr) [content-start] min(var(--content-max-width), 100%) [content-end] minmax(0, 1fr) [wide-end] var(--grid-margin) [full-end]; grid-template-rows: auto 1fr auto; }
/* Content aligns to content area */ body > * { grid-column: content; }
/* Full-bleed elements span full width */ body > [data-layout="full"] { grid-column: full; }
/* Wide elements extend past content */ body > [data-layout="wide"] { grid-column: wide; } }
Responsive Card Grids
Auto-Fit Pattern (Recommended)
Cards flow into available columns automatically:
@layer components { card-grid { display: grid; grid-template-columns: repeat( auto-fit, minmax(min(100%, 18rem), 1fr) ); gap: var(--grid-gutter); } }
How it works:
-
auto-fit creates as many columns as fit
-
minmax(min(100%, 18rem), 1fr) ensures cards are at least 18rem but stretch to fill space
-
min(100%, 18rem) prevents overflow on narrow viewports
Auto-Fill vs Auto-Fit
Property Behavior Use When
auto-fit
Collapses empty tracks Cards should stretch to fill row
auto-fill
Keeps empty tracks Maintain consistent column widths
/* auto-fit: cards stretch */ grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
/* auto-fill: consistent widths, may leave gaps */ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
Constrained Column Count
Limit maximum columns while staying fluid:
card-grid { --_min-card-width: 18rem; --_max-columns: 4;
display: grid; grid-template-columns: repeat( auto-fit, minmax( max( var(--_min-card-width), calc((100% - var(--grid-gutter) * (var(--_max-columns) - 1)) / var(--_max-columns)) ), 1fr ) ); gap: var(--grid-gutter); }
Explicit Column Grids
12-Column Grid
@layer layout { [data-grid="12"] { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gutter); }
/* Span utilities */ [data-span="1"] { grid-column: span 1; } [data-span="2"] { grid-column: span 2; } [data-span="3"] { grid-column: span 3; } [data-span="4"] { grid-column: span 4; } [data-span="5"] { grid-column: span 5; } [data-span="6"] { grid-column: span 6; } [data-span="7"] { grid-column: span 7; } [data-span="8"] { grid-column: span 8; } [data-span="9"] { grid-column: span 9; } [data-span="10"] { grid-column: span 10; } [data-span="11"] { grid-column: span 11; } [data-span="12"] { grid-column: span 12; } }
Responsive Column Spans
Use container queries for component-level responsiveness:
@layer components { feature-grid { container-type: inline-size; display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gutter); }
feature-grid > * { grid-column: span 12; /* Full width by default */ }
@container (min-width: 30rem) { feature-grid > * { grid-column: span 6; /* Half width */ } }
@container (min-width: 50rem) { feature-grid > * { grid-column: span 4; /* Third width */ }
feature-grid > :first-child {
grid-column: span 8; /* Featured item wider */
}
} }
Subgrid for Alignment
Subgrid enables nested elements to align with parent grid tracks.
Card Grid with Aligned Content
@layer components { /* Parent defines the column structure / product-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); / Define row tracks for card internals / grid-auto-rows: auto 1fr auto; / image, content, actions */ gap: var(--grid-gutter); }
/* Cards span 3 rows and inherit row tracks */ product-card { display: grid; grid-row: span 3; grid-template-rows: subgrid; gap: var(--size-m); }
product-card img { grid-row: 1; } product-card .content { grid-row: 2; } product-card .actions { grid-row: 3; } }
Form Alignment with Subgrid
@layer components { form { display: grid; grid-template-columns: max-content 1fr; gap: var(--size-m); }
form-field { display: grid; grid-column: span 2; grid-template-columns: subgrid; }
form-field label { grid-column: 1; }
form-field input, form-field select { grid-column: 2; } }
Asymmetric Layouts
Content + Sidebar
@layer layout { [data-layout="sidebar"] { display: grid; grid-template-columns: 1fr min(20rem, 30%); gap: var(--grid-gutter); }
/* Responsive: stack on narrow */ @container (max-width: 50rem) { [data-layout="sidebar"] { grid-template-columns: 1fr; } } }
Golden Ratio Split
@layer layout { [data-layout="golden"] { display: grid; /* 1.618:1 ratio */ grid-template-columns: 1.618fr 1fr; gap: var(--grid-gutter); } }
Feature + Gallery
@layer layout { [data-layout="feature-gallery"] { display: grid; grid-template-columns: 2fr 1fr 1fr; grid-template-rows: 1fr 1fr; gap: var(--grid-gutter); }
[data-layout="feature-gallery"] > :first-child { grid-row: span 2; } }
Fluid Gap Scaling
Gap Token Scale
:root { /* Gaps scale with viewport */ --gap-xs: clamp(0.25rem, 0.125rem + 0.5vw, 0.5rem); --gap-sm: clamp(0.5rem, 0.25rem + 1vw, 1rem); --gap-md: clamp(1rem, 0.5rem + 2vw, 2rem); --gap-lg: clamp(1.5rem, 0.75rem + 3vw, 3rem); --gap-xl: clamp(2rem, 1rem + 4vw, 4rem); }
Using Gap Tokens
card-grid { gap: var(--gap-md); }
section-grid { gap: var(--gap-lg); }
icon-grid { gap: var(--gap-sm); }
Resolution-Independent Patterns
Using Proportional Units
Unit Use For Example
fr
Flexible column widths 1fr 2fr 1fr
%
Proportional within container max-width: 80%
vw/vh
Viewport-relative width: 50vw
cqi/cqw
Container-relative width: 50cqi
rem
Root-relative sizing gap: 1.5rem
ch
Character-based width max-width: 65ch
Never Use Fixed Pixels For:
-
Column widths
-
Gutters and gaps
-
Margins and padding
-
Container max-widths (use rem instead)
Pixel-Safe Uses:
-
Border widths (1px , 2px )
-
Box shadows
-
Fine visual details
Common Layout Patterns
Holy Grail Layout
body { display: grid; grid-template: "header header header" auto "nav main aside" 1fr "footer footer footer" auto / minmax(10rem, 15rem) 1fr minmax(10rem, 20rem); min-height: 100vh; gap: var(--grid-gutter); }
header { grid-area: header; } nav { grid-area: nav; } main { grid-area: main; } aside { grid-area: aside; } footer { grid-area: footer; }
/* Stack on mobile */ @media (max-width: 60rem) { body { grid-template: "header" auto "nav" auto "main" 1fr "aside" auto "footer" auto / 1fr; } }
Magazine Layout
article-layout { display: grid; grid-template-columns: [full-start] 1fr [main-start] minmax(0, 65ch) [main-end] 1fr [full-end]; gap: var(--grid-gutter); }
article-layout > * { grid-column: main; }
article-layout > figure, article-layout > blockquote { grid-column: full; }
Masonry-Style (CSS Grid Approximation)
masonry-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); grid-auto-rows: 1rem; gap: var(--grid-gutter); }
masonry-grid > * { /* Items span variable rows based on content */ grid-row: span var(--rows, 10); }
Note: True masonry requires JavaScript to calculate --rows or wait for CSS masonry property support.
Integration with Container Queries
Component-Level Grid Responsiveness
@layer components { dashboard-widget { container-type: inline-size; display: grid; gap: var(--size-m); }
/* Single column by default */ dashboard-widget { grid-template-columns: 1fr; }
/* Two columns when widget is wide enough */ @container (min-width: 25rem) { dashboard-widget { grid-template-columns: 1fr 1fr; } }
/* Three columns with sidebar */ @container (min-width: 45rem) { dashboard-widget { grid-template-columns: 2fr 1fr 1fr; } } }
Debug Grid Overlay
Visualize grid during development:
/* Add to :root for development */ :root { --debug-grid: 0; }
data-grid, [data-layout] { position: relative; }
[data-layout]::before { content: ""; position: absolute; inset: 0; pointer-events: none; background: repeating-linear-gradient( 90deg, oklch(60% 0.15 250 / 0.1) 0, oklch(60% 0.15 250 / 0.1) var(--grid-column-width), transparent var(--grid-column-width), transparent calc(var(--grid-column-width) + var(--grid-gutter)) ); opacity: var(--debug-grid); z-index: 9999; }
Toggle with: document.documentElement.style.setProperty('--debug-grid', '1')
Checklist
When implementing grid layouts:
Tokens
-
Grid gutter uses clamp() for fluid scaling
-
Page margin scales with viewport
-
No fixed pixel values for layout dimensions
-
Content max-width defined in rem
Grid Structure
-
Use auto-fit /auto-fill for card grids
-
minmax() includes min(100%, value) to prevent overflow
-
Named grid areas for page layout
-
fr units for flexible column widths
Responsiveness
-
Container queries for component-level changes
-
Media queries only for page-level layout shifts
-
Single grid system works across all viewports
-
Test at arbitrary widths, not just standard breakpoints
Alignment
-
Subgrid used for nested element alignment
-
Consistent gap tokens throughout
-
Items align to baseline grid where appropriate
Related Skills
-
css-author - @layer organization, container queries, @scope
-
typography - Baseline grid, vertical rhythm
-
responsive-images - Images in grid contexts
-
progressive-enhancement - CSS-only responsive patterns