Episode 1 — Fundamentals / 1.9 — CSS Responsive Design
1.9.f — Spacing & Rhythm
In one sentence: A consistent spacing system built on a base unit (4px or 8px) eliminates visual randomness, speeds up development decisions, and makes responsive adjustments predictable.
Navigation: ← 1.9.e — Fluid Typography · 1.9.g — CSS Frameworks Overview →
1. What is vertical rhythm?
Vertical rhythm means the vertical spacing between elements follows a consistent pattern — like lines on ruled paper. When rhythm is consistent, the page feels balanced and professional. When it's random, the design looks thrown together.
Good rhythm (consistent) Poor rhythm (random)
┌──────────────────┐ ┌──────────────────┐
│ Heading │ 24px │ Heading │ 24px
│ │ │ │
│ Paragraph │ 16px │ Paragraph │ 13px
│ │ │ │
│ Paragraph │ 16px │ │
│ │ │ Paragraph │ 22px
│ Subheading │ 24px │ │
│ │ │ Subheading │ 8px
│ Paragraph │ 16px │ Paragraph │ 16px
└──────────────────┘ └──────────────────┘
2. Choosing a base unit
Most design systems use 4px or 8px as the base unit. Every spacing value is a multiple of that base.
4px base (more granular)
4 → 0.25rem
8 → 0.5rem
12 → 0.75rem
16 → 1rem
20 → 1.25rem
24 → 1.5rem
32 → 2rem
40 → 2.5rem
48 → 3rem
64 → 4rem
8px base (simpler, fewer choices)
8 → 0.5rem
16 → 1rem
24 → 1.5rem
32 → 2rem
48 → 3rem
64 → 4rem
96 → 6rem
Why multiples work: Consistent increments align elements on a visual grid, making the design feel structured. Sub-pixel rounding issues are minimized with clean multiples.
3. Spacing scale tokens
Encode your scale as CSS custom properties so every developer uses the same values:
:root {
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
--space-20: 5rem; /* 80px */
--space-24: 6rem; /* 96px */
}
Usage:
.card {
padding: var(--space-4);
margin-bottom: var(--space-6);
}
.section {
padding-block: var(--space-12);
}
.stack > * + * {
margin-top: var(--space-4);
}
Now if you need to audit or adjust spacing globally, you change the tokens — not hundreds of individual values.
4. Horizontal spacing: margins and padding
Horizontal spacing follows the same scale but serves different purposes:
| Property | Use case | Example |
|---|---|---|
padding-inline | Internal breathing room | Card content away from edges |
margin-inline: auto | Centering block elements | Container centering |
gap | Space between flex/grid children | Grid gutter |
column-gap | Horizontal grid/flex gap | Multi-column layouts |
.container {
max-width: 1200px;
margin-inline: auto;
padding-inline: var(--space-4);
}
@media (min-width: 768px) {
.container {
padding-inline: var(--space-8);
}
}
5. Using gap for consistent component spacing
Before gap, developers used margins on children and :first-child / :last-child hacks to remove extra space. gap eliminates this entirely:
/* Old way — fragile */
.list-item {
margin-bottom: 1rem;
}
.list-item:last-child {
margin-bottom: 0;
}
/* Modern way — clean */
.list {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
gap works in Flexbox, Grid, and multi-column layouts:
/* Flex row with gap */
.tag-list {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
/* Grid with different row/column gaps */
.dashboard {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: var(--space-6);
row-gap: var(--space-8);
}
6. The "stack" and "cluster" patterns
Stack — vertical spacing between siblings
.stack {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
Every direct child is spaced identically. No margin management needed.
Cluster — horizontal wrapping with consistent gaps
.cluster {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
align-items: center;
}
Tags, badges, or inline actions wrap naturally with uniform spacing.
Stack (vertical) Cluster (horizontal wrap)
┌──────────────┐ ┌──────────────────────┐
│ Item A │ │ [Tag] [Tag] [Tag] │
│ ↕ gap │ │ ↔gap ↔gap │
│ Item B │ │ [Tag] [Tag] │
│ ↕ gap │ └──────────────────────┘
│ Item C │
└──────────────┘
7. Responsive spacing that scales
Use clamp() for spacing that grows with the viewport — same technique as fluid typography:
:root {
--space-section: clamp(2rem, 5vw, 6rem);
--space-card: clamp(1rem, 2vw, 2rem);
--space-gutter: clamp(1rem, 3vw, 3rem);
}
.hero {
padding-block: var(--space-section);
}
.card {
padding: var(--space-card);
}
.grid {
gap: var(--space-gutter);
}
Or change tokens at breakpoints:
:root {
--space-section: 2rem;
}
@media (min-width: 768px) {
:root { --space-section: 4rem; }
}
@media (min-width: 1200px) {
:root { --space-section: 6rem; }
}
Both approaches work. clamp() is smoother; breakpoint-based is more explicit.
8. Avoiding magic numbers
A magic number is a hard-coded value with no clear origin:
/* Bad — where did these come from? */
.header { padding: 13px 27px; margin-bottom: 43px; }
Problems:
- No relationship to other spacing on the page.
- Impossible to adjust globally.
- Creates visual inconsistency.
Fix: Use your spacing tokens:
/* Good — from the system */
.header {
padding: var(--space-3) var(--space-6);
margin-bottom: var(--space-10);
}
When a token doesn't fit: If you need 13px, you probably need 12px (--space-3) or 16px (--space-4). Round to the nearest token. If you truly need a one-off value, comment why:
.logo {
/* Optical alignment: the logo's internal padding makes 12px look off-center */
margin-top: 14px;
}
9. Putting it all together
:root {
--space-2: 0.5rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
--space-16: 4rem;
}
body {
font-size: clamp(1rem, 0.34vw + 0.91rem, 1.125rem);
line-height: 1.6;
}
.page {
max-width: 1200px;
margin-inline: auto;
padding-inline: var(--space-4);
}
.section {
padding-block: clamp(var(--space-8), 5vw, var(--space-16));
}
.section > * + * {
margin-top: var(--space-4);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--space-6);
}
.card {
padding: var(--space-6);
border-radius: var(--space-2);
}
10. Key takeaways
- Pick a base unit (4px or 8px) and build every spacing value from multiples of it.
- Encode the scale as CSS custom properties (tokens) — never hard-code random values.
- Use
gapon flex/grid instead of margin hacks on children. - Stack and cluster patterns cover most vertical and horizontal spacing needs.
- Make spacing responsive with
clamp()or breakpoint-based token updates. - Eliminate magic numbers — round to the nearest token and document exceptions.
Explain-It Challenge
Explain without notes:
- Why does a 4px/8px spacing system make design reviews and code reviews faster?
- Your colleague uses
margin-bottomon every child plus:last-child { margin-bottom: 0 }. What's a cleaner modern approach? - How would you make section padding scale smoothly from
2remon mobile to6remon desktop without breakpoints?
Navigation: ← 1.9.e — Fluid Typography · 1.9.g — CSS Frameworks Overview →