Master the 8-point grid system and CSS spacing scales — build a token-based spacing system using CSS custom properties that creates visual rhythm, ensures consistency across components, and scales predictably from mobile to desktop.
If visual hierarchy is what guides the user's eye to the right information, spacing is what makes that guidance feel effortless rather than crowded. Spacing creates breathing room, separates distinct content regions, and groups related elements — all without a single color or font change.
The 8-point grid is the most widely adopted spacing system in professional UI design. It was popularized by Google's Material Design and adopted by every major design system including Tailwind CSS, Chakra UI, Radix, and the GitHub Primer. The principle is simple: every spacing value in your design is a multiple of 8 (or 4 for smaller increments). This constraint eliminates the thousand arbitrary pixel values that accumulate in codebases over time and make spacing feel inconsistent.
This lesson builds a complete spacing system using CSS custom properties — a token-based approach that gives you the consistency of a design system and the flexibility of plain CSS.
The 8-point grid aligns with several technical realities of modern screens:
Two variants of the grid:
The combined approach: use 4 for fine spacing (borders, icon padding), 8 for component padding, 16+ for layout spacing:
1:root {
2 /* 4-point base grid — every value is a multiple of 4px */
3 --space-0: 0; /* 0px — intentional zero */
4 --space-px: 1px; /* 1px — border-width only */
5 --space-0-5: 0.125rem; /* 2px — micro adjustments only */
6 --space-1: 0.25rem; /* 4px — minimum meaningful space */
7 --space-1-5: 0.375rem; /* 6px — compact padding */
8 --space-2: 0.5rem; /* 8px — tight component spacing */
9 --space-2-5: 0.625rem; /* 10px — medium-compact */
10 --space-3: 0.75rem; /* 12px — comfortable component padding */
11 --space-4: 1rem; /* 16px — standard component padding */
12 --space-5: 1.25rem; /* 20px — generous component padding */
13 --space-6: 1.5rem; /* 24px — large component padding */
14 --space-8: 2rem; /* 32px — section divider spacing */
15 --space-10: 2.5rem; /* 40px — large gaps */
16 --space-12: 3rem; /* 48px — section padding */
17 --space-16: 4rem; /* 64px — major section spacing */
18 --space-20: 5rem; /* 80px — large layout gaps */
19 --space-24: 6rem; /* 96px — hero padding */
20 --space-32: 8rem; /* 128px — maximum section spacing */
21}Note the Tailwind CSS correspondence: --space-4 = 1rem = Tailwind's 'p-4'. This alignment means Tailwind utilities and your custom properties are interchangeable.
Spacing in a UI system operates at two distinct scales that should never be mixed:
Component spacing: the internal padding and gap within a UI component. Typically uses smaller tokens (space-2 through space-6). This is the breathing room inside a card, button, or form field.
Layout spacing: the margin between components and sections on a page. Typically uses larger tokens (space-8 through space-32). This is the air between a hero section and the features section.
The fundamental rule: never put a layout-scale spacing value inside a component, and never use a component-scale spacing value for layout gaps. Mixing them creates interfaces where components feel cramped next to generously spaced sections.
1/* Component spacing — internal to the component */
2.button {
3 padding: var(--space-3) var(--space-6); /* 12px vertical, 24px horizontal */
4 gap: var(--space-2); /* 8px icon-to-label gap */
5 border-radius: var(--space-2); /* 8px border-radius */
6 border: 1px solid transparent;
7 /* Total height: 12 + 16 + 12 = 40px (standard button height) */
8}
9
10.button.size-sm {
11 padding: var(--space-2) var(--space-4); /* 8px / 16px — compact */
12 font-size: 0.875rem;
13}
14
15.button.size-lg {
16 padding: var(--space-4) var(--space-8); /* 16px / 32px — large */
17 font-size: 1.0625rem;
18}
19
20.card {
21 padding: var(--space-6); /* 24px — card internal padding */
22 border-radius: var(--space-3); /* 12px border-radius */
23}
24
25.card-header {
26 padding-bottom: var(--space-4); /* 16px — header to body gap */
27 border-bottom: 1px solid var(--color-border);
28 margin-bottom: var(--space-4);
29}
30
31/* Layout spacing — between components on a page */
32.page-section {
33 padding-block: var(--space-24); /* 96px top/bottom section padding */
34}
35
36.section-header {
37 margin-bottom: var(--space-12); /* 48px — heading to content gap */
38}
39
40.features-grid {
41 display: grid;
42 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
43 gap: var(--space-6); /* 24px — between feature cards */
44}Vertical rhythm is the consistent spacing pattern that flows through a page's content hierarchy. When vertical spacing is rhythmic, the page feels organized and easy to scan. When it is arbitrary, the page feels cramped in some places and wasteful in others.
A vertical rhythm system assigns specific spacing values to the semantic relationships between content elements:
| Relationship | Spacing token | Value |
|---|---|---|
| Heading to following paragraph | --space-3 (12px) | tight — they are a pair |
| Paragraph to next paragraph | --space-5 (20px) | comfortable separation |
| Section to next section | --space-16 (64px) | clear break |
| Page header to main content | --space-12 (48px) | substantial gap |
1/* Vertical rhythm system — applied globally */
2h1, h2, h3, h4, h5, h6 {
3 margin-top: 0;
4 /* Heading-to-content gap is small — heading introduces the following content */
5 margin-bottom: var(--space-3);
6}
7
8p { margin-top: 0; margin-bottom: var(--space-5); }
9p:last-child { margin-bottom: 0; }
10
11/* Larger gap before a heading — it starts a new section */
12* + h2 { margin-top: var(--space-16); }
13* + h3 { margin-top: var(--space-10); }
14* + h4 { margin-top: var(--space-8); }
15
16/* The owl selector (* + *) adds top margin to all elements
17 that follow another element — elegant spacing with one rule */
18.prose * + * { margin-top: var(--space-5); }
19.prose * + h2 { margin-top: var(--space-16); }
20.prose * + h3 { margin-top: var(--space-10); }
21
22/* Page-level section rhythm */
23section { padding-block: var(--space-20); }
24section + section { padding-top: 0; } /* avoid double spacing between sections */
25
26/* Lobotomized owl for layout grids */
27.stack > * + * { margin-top: var(--space-6); }
28.stack-compact > * + * { margin-top: var(--space-3); }
29.stack-loose > * + * { margin-top: var(--space-10); }The lobotomized owl selector (* + *) is a powerful layout primitive: it adds a top margin to every element that is preceded by another element. This creates a uniform stack spacing pattern without wrapping every element in a container.
Modern CSS layout uses the gap property in CSS Grid and Flexbox instead of margins between items. Gap is superior because it does not create extra space at the edges of a container — it only adds space between items.
1/* Grid layout spacing from the scale */
2.course-grid {
3 display: grid;
4 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
5 gap: var(--space-6); /* 24px between all grid items */
6}
7
8/* Different row and column gaps */
9.dashboard-grid {
10 display: grid;
11 grid-template-columns: repeat(12, 1fr);
12 column-gap: var(--space-6); /* 24px between columns */
13 row-gap: var(--space-8); /* 32px between rows */
14}
15
16/* Flex layout spacing */
17.nav-items {
18 display: flex;
19 gap: var(--space-1); /* 4px between nav items */
20 align-items: center;
21}
22
23.form-row {
24 display: flex;
25 gap: var(--space-4); /* 16px between form fields in a row */
26 flex-wrap: wrap;
27}
28
29/* Icon + label spacing in a button — very tight */
30.btn {
31 display: inline-flex;
32 align-items: center;
33 gap: var(--space-2); /* 8px icon to label */
34}
35
36/* Tag cloud — flexible gaps */
37.tag-list {
38 display: flex;
39 flex-wrap: wrap;
40 gap: var(--space-2) var(--space-3); /* 8px row gap, 12px column gap */
41}A spacing-token approach to gap removes all guesswork from layout. When a designer says 'put 24px between the cards,' that maps directly to gap: var(--space-6). When they say 'tighten the icon spacing,' that means gap: var(--space-1) (4px). The conversation becomes about design intent, not pixel values.
A three-tier pricing page demonstrating the 8-point grid applied consistently across page layout, section padding, card padding, and component gap values.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Pricing — Spacing System Demo</title>
7 <style>
8 :root {
9 --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem;
10 --space-4: 1rem; --space-5: 1.25rem; --space-6: 1.5rem;
11 --space-8: 2rem; --space-10: 2.5rem; --space-12: 3rem;
12 --space-16: 4rem; --space-20: 5rem; --space-24: 6rem;
13
14 --color-bg: #f8fafc;
15 --color-surface: #ffffff;
16 --color-border: #e2e8f0;
17 --color-text-primary: #0f172a;
18 --color-text-secondary: #334155;
19 --color-text-muted: #64748b;
20 --color-brand: #2563eb;
21 --color-brand-hover: #1d4ed8;
22 --color-brand-subtle: #eff6ff;
23 }
24 *, *::before, *::after { box-sizing: border-box; }
25 body { margin: 0; background: var(--color-bg);
26 font-family: system-ui, sans-serif; color: var(--color-text-secondary); }
27
28 /* Page-level layout spacing */
29 .pricing-page {
30 max-width: 1000px;
31 margin: 0 auto;
32 padding: var(--space-20) var(--space-6);
33 }
34
35 /* Section header spacing */
36 .section-header { text-align: center; margin-bottom: var(--space-12); }
37 .section-label {
38 display: inline-block;
39 font-size: 0.75rem;
40 font-weight: 700;
41 text-transform: uppercase;
42 letter-spacing: 0.1em;
43 color: var(--color-brand);
44 margin-bottom: var(--space-3);
45 }
46 .section-title {
47 font-size: clamp(2rem, 5vw, 2.75rem);
48 font-weight: 800;
49 color: var(--color-text-primary);
50 margin: 0 0 var(--space-4);
51 line-height: 1.15;
52 }
53 .section-subtitle {
54 font-size: 1.125rem;
55 color: var(--color-text-muted);
56 margin: 0;
57 max-width: 48ch;
58 margin-inline: auto;
59 line-height: 1.65;
60 }
61
62 /* Pricing grid — layout-scale gap */
63 .pricing-grid {
64 display: grid;
65 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
66 gap: var(--space-6); /* 24px between cards — layout-scale spacing */
67 }
68
69 /* Pricing card — component-scale padding */
70 .pricing-card {
71 background: var(--color-surface);
72 border: 1.5px solid var(--color-border);
73 border-radius: var(--space-4); /* 16px border-radius */
74 padding: var(--space-8); /* 32px card padding — generous */
75 display: flex;
76 flex-direction: column;
77 gap: var(--space-6); /* 24px between card sections */
78 }
79 .pricing-card.featured {
80 border-color: var(--color-brand);
81 box-shadow: 0 0 0 4px hsl(220 90% 56% / 0.1);
82 }
83
84 /* Plan name and price — component internal spacing */
85 .plan-name {
86 font-size: 0.9375rem;
87 font-weight: 700;
88 text-transform: uppercase;
89 letter-spacing: 0.05em;
90 color: var(--color-text-muted);
91 margin: 0;
92 }
93 .plan-price {
94 display: flex;
95 align-items: baseline;
96 gap: var(--space-1); /* 4px — tightest gap for price unit */
97 margin-top: var(--space-2);
98 }
99 .plan-price .amount {
100 font-size: 3rem;
101 font-weight: 800;
102 color: var(--color-text-primary);
103 line-height: 1;
104 }
105 .plan-price .period {
106 font-size: 0.875rem;
107 color: var(--color-text-muted);
108 }
109
110 /* Feature list — component spacing */
111 .feature-list { list-style: none; padding: 0; margin: 0; display: flex;
112 flex-direction: column; gap: var(--space-3); }
113 .feature-item {
114 display: flex;
115 align-items: flex-start;
116 gap: var(--space-3); /* 12px — icon to text */
117 font-size: 0.9375rem;
118 color: var(--color-text-secondary);
119 }
120 .feature-check { color: #16a34a; font-weight: 700; flex-shrink: 0; }
121
122 /* CTA button */
123 .plan-btn {
124 display: block;
125 width: 100%;
126 padding: var(--space-4) var(--space-6); /* 16px / 24px — large button */
127 border-radius: var(--space-2); /* 8px */
128 font-size: 1rem;
129 font-weight: 700;
130 font-family: inherit;
131 cursor: pointer;
132 border: 1.5px solid transparent;
133 text-align: center;
134 transition: background-color 150ms ease, border-color 150ms ease;
135 margin-top: auto;
136 }
137 .plan-btn-primary {
138 background: var(--color-brand);
139 color: white;
140 border-color: var(--color-brand);
141 }
142 .plan-btn-primary:hover { background: var(--color-brand-hover); border-color: var(--color-brand-hover); }
143 .plan-btn-secondary {
144 background: white;
145 color: var(--color-brand);
146 border-color: var(--color-border);
147 }
148 .plan-btn-secondary:hover { border-color: var(--color-brand); background: var(--color-brand-subtle); }
149 </style>
150</head>
151<body>
152 <div class="pricing-page">
153 <header class="section-header">
154 <span class="section-label">Pricing</span>
155 <h1 class="section-title">Learn without limits</h1>
156 <p class="section-subtitle">Start free and upgrade when you're ready. All plans include a 30-day money-back guarantee.</p>
157 </header>
158 <div class="pricing-grid">
159 <div class="pricing-card">
160 <div>
161 <p class="plan-name">Free</p>
162 <div class="plan-price">
163 <span class="amount">$0</span>
164 <span class="period">/month</span>
165 </div>
166 </div>
167 <ul class="feature-list">
168 <li class="feature-item"><span class="feature-check">✓</span> 10 free courses</li>
169 <li class="feature-item"><span class="feature-check">✓</span> Community access</li>
170 <li class="feature-item"><span class="feature-check">✓</span> Course previews</li>
171 </ul>
172 <button class="plan-btn plan-btn-secondary" type="button">Get started free</button>
173 </div>
174 <div class="pricing-card featured">
175 <div>
176 <p class="plan-name">Pro</p>
177 <div class="plan-price">
178 <span class="amount">$29</span>
179 <span class="period">/month</span>
180 </div>
181 </div>
182 <ul class="feature-list">
183 <li class="feature-item"><span class="feature-check">✓</span> All 200+ courses</li>
184 <li class="feature-item"><span class="feature-check">✓</span> Completion certificates</li>
185 <li class="feature-item"><span class="feature-check">✓</span> Offline downloads</li>
186 <li class="feature-item"><span class="feature-check">✓</span> Priority support</li>
187 </ul>
188 <button class="plan-btn plan-btn-primary" type="button">Start Pro — 30 days free</button>
189 </div>
190 <div class="pricing-card">
191 <div>
192 <p class="plan-name">Team</p>
193 <div class="plan-price">
194 <span class="amount">$79</span>
195 <span class="period">/month</span>
196 </div>
197 </div>
198 <ul class="feature-list">
199 <li class="feature-item"><span class="feature-check">✓</span> Everything in Pro</li>
200 <li class="feature-item"><span class="feature-check">✓</span> Up to 10 seats</li>
201 <li class="feature-item"><span class="feature-check">✓</span> Team analytics</li>
202 <li class="feature-item"><span class="feature-check">✓</span> Dedicated support</li>
203 </ul>
204 <button class="plan-btn plan-btn-secondary" type="button">Contact sales</button>
205 </div>
206 </div>
207 </div>
208</body>
209</html>Every spacing value in this pricing page references a custom property from the 8-point scale. Page padding uses --space-20 (80px) and --space-6 (24px). The section header margin uses --space-12 (48px). The grid gap uses --space-6 (24px — layout scale). Inside each card: padding is --space-8 (32px — component scale), gaps between sections use --space-6 (24px), feature list gaps use --space-3 (12px — tight for related items), and the icon-to-text gap uses --space-3 (12px). The price amount to period gap uses --space-1 (4px — very tight, they are a unit). No arbitrary pixel values appear anywhere in the spacing system.
The next lesson covers layout principles — the 12-column grid, CSS Grid layout patterns, and how to translate column-based layout annotations from Figma into production-ready CSS Grid code.
Your progress is saved in this browser, on this device only.