# Spatial Rhythm And Whitespace

> Apply the spacing, container, grid, and border-radius rhythm of premium VC-funded sites. Generous 80-160px section padding, intentional non-strict spacing scales (including 20/28/40), deliberate radius architecture (binary 0-or-pill, asymmetric interlocking, per-element variety), 40px paragraph-spacing on h2, 1px hairline dividers at 4-10% opacity. Use when layouts feel cramped, monotonous, or use "rounded-md everywhere."

- Skill: `automattic/spatial-rhythm-and-whitespace` (Agent Skill)
- Install (CLI): `npx skillmds@latest add automattic/spatial-rhythm-and-whitespace`
- Raw SKILL.md: https://api.skillmd.com/api/skills/automattic/spatial-rhythm-and-whitespace/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Automattic (https://skillmd.com/u/automattic)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/automattic/spatial-rhythm-and-whitespace

---


# Spatial Rhythm and Whitespace

The way space is used separates premium sites from templates. Extracted from Cal.com, Duna, Antimetal, Cohere, AngelList, Incident, Hex, Reducto.

## When to invoke

- Sections are `py-12` or `py-16` across the whole page
- Every element uses `rounded-md` (8px) or `rounded-lg` (8px)
- Cards are tight against each other with 16px gaps
- The `h2 → p` gap is default `1em` line-height
- Container width feels arbitrary
- User says design feels "cramped" or "monotonous"

## The 7 core rules

### Rule 1: Border-radius is a designed system, not a default

Premium sites use **different radii per element type**. Generic sites use one radius everywhere.

**Four radius architectures** observed in case studies — pick ONE:

**(A) Duna's binary system** — only `0` OR `9999px` (pill):
```css
/* Sharp rectangular cards */
.card { border-radius: 0; }
/* Fully pill buttons */
.btn  { border-radius: 9999px; }
/* Nothing in between — no rounded-md 8px */
```
This is the most aggressive and recognizable move.

**(B) Cohere's asymmetric interlocking** — `20px` on outer corners, `0` at joins:
```css
/* Card in a 2x2 grid, top-left position */
.grid-card:nth-child(1) { border-radius: 20px 0 0 20px; }
.grid-card:nth-child(2) { border-radius: 0 20px 20px 0; }
.grid-card:nth-child(3) { border-radius: 20px 0 0 18px; }
.grid-card:nth-child(4) { border-radius: 0 20px 18px 0; }

/* Stitched "top of section" panel */
.panel-top { border-radius: 20px 20px 0 0; }
/* Stitched "bottom" */
.panel-bottom { border-radius: 0 0 18px 20px; }
```
Cards visually lock together like quilt tiles.

**(C) Per-element variety** (Hex, Cal, AngelList) — radius matches semantic role:
```css
.btn-pill   { border-radius: 9999px; } /* primary pills */
.btn-chip   { border-radius: 30px; }   /* chips and secondary */
.card       { border-radius: 12px; }   /* most cards */
.card-large { border-radius: 20px; }   /* feature cards */
.input      { border-radius: 8px; }
.avatar     { border-radius: 50%; }
.tag        { border-radius: 2px; }    /* micro chips */
.feature    { border-radius: 40px; }   /* large hero elements */
```

**(D) Extreme-radius for art** (AngelList) — 200–300px radius creates "stadium" shapes:
```css
.art-plate { border-radius: 300px; overflow: hidden; aspect-ratio: 2/1; }
```

Anti-pattern: `border-radius: 8px` on every card, button, input, image.

### Rule 2: Section padding is 80-160px, not 48-64px

Premium sites breathe. Section vertical padding is generous:

```css
/* Minimum acceptable */
.section { padding-block: 80px; }

/* Premium default */
.section { padding-block: 120px; }

/* Hero/marketing sections */
.section-hero { padding-block: 140px 96px; }

/* Editorial/rhythm-heavy sections */
.section-editorial { padding-block: 160px; }
```

**Duna's observed values**: `128px 40px`, `140px 40px`, `128px 40px 96px` — vertical padding dominates, paired with fixed horizontal.

**Incident / AngelList**: section padding ranges `80-160px` fluid.

**Amplemarket**: `120-200px` on desktop.

### Rule 3: Spacing scales include non-4-multiples

Strict 4/8/12/16 multiples are a tell. Premium sites **include oddballs** — 20, 28, 40, 64, 96, 128, 140.

**Observed scales**:

| Site | Spacing scale |
|---|---|
| Duna | 4, 8, 12, 16, **20**, 24, 40, 64, 80, 96, 128 |
| Cal | 4, 8, 12, 16, 24, **28**, 40 |
| Hex | 4, 8, **10**, 12, 16, **20**, 24, 32, 40, 48, 60, 80, 120 |
| Cohere | 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 128, **160** |
| Antimetal | (Tailwind v4 arbitrary-multiples — `w-120`, `calc(1120*1px)`) |

**The 20px and 28px entries** break the strict-4-multiples habit. Use them for:
- 20px: intermediate padding between "tight" (16px) and "comfortable" (24px)
- 28px: h4 tier, card-body pairings

Anti-pattern: `gap-4 p-4 m-4` (Tailwind monotone).

### Rule 4: Paragraph spacing is explicit and generous

After `h2`, set a deliberate paragraph-spacing of **40px** (Cal's observed default). Don't rely on margins from line-height.

```css
h2 { margin-block-end: 40px; }      /* Cal's spec */
h3 { margin-block-end: 24px; }
p + p { margin-block-start: 1.25em; }
```

**Framer's `paragraphSpacing` pattern** (from Cal.com and Duna):
```css
h2 { padding-block-end: 40px; }
p { padding-block-end: 20px; }
```

### Rule 5: Container widths are deliberate, and 1200-1280px is the sweet spot

```css
/* Most common premium width */
.container { max-width: 1280px; margin-inline: auto; padding-inline: 24px; }

/* Reducto */
.container-reducto { max-width: 1280px; }

/* Editorial/reading */
.container-prose { max-width: 720px; }

/* Feature/hero blocks */
.container-wide { max-width: 1440px; }
```

**Outseta** uses `width: 88%` of viewport (percent-based — more forgiving at wide screens).
**Antimetal** has multiple: `400px, 568px, 768px, 992px, 1200px, 1536px`.

**Horizontal padding** should scale with viewport:
```css
.container {
  padding-inline: clamp(16px, 4vw, 80px);
}
```

### Rule 6: Hairline dividers at 4-11% alpha, never solid

Premium sites use hairlines with LOW opacity:

```css
/* Observed hairline tokens */
--hairline-subtle:  rgba(0, 0, 0, 0.04);     /* Duna */
--hairline-light:   rgba(0, 0, 0, 0.06);     /* Hex, most common */
--hairline-medium:  rgba(0, 0, 0, 0.10);     /* default */
--hairline-navy:    rgba(12, 38, 77, 0.04);  /* Antimetal (theme-tinted) */
--hairline-warm:    rgba(0, 45, 51, 0.10);   /* AngelList */

/* Use as 1px border */
hr { border: 0; border-top: 1px solid var(--hairline-light); }

/* Better: use as box-shadow 1px ring (common pattern) */
.card { box-shadow: 0 0 0 1px var(--hairline-light); }

/* Bottom-line shadow trick (AngelList's "shadow-b") */
.nav-item { box-shadow: 0 1px 0 #002d331a; }
```

Anti-pattern: `border: 1px solid #e5e7eb` (cold/generic).

### Rule 7: Grid gaps match typographic rhythm

Gaps between cards should echo the spacing of text below them. Options:

```css
/* Tight editorial grid (like a newspaper) — 8-12px gaps */
.grid-editorial { gap: 12px; }

/* Standard content grid — 16-24px */
.grid-cards { gap: 24px; }

/* Spacious feature grid — 32-48px */
.grid-features { gap: 40px; }
```

**Reducto** uses tight `2-16px` gaps between components with generous `py-16 to py-32` section padding — **small gaps, big sections** is a deliberate rhythm.

**AngelList** has tight internal content with giant section breaks.

## Complete spacing system starter

```css
:root {
  /* Spacing scale — includes non-4 multiples */
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;    /* breaker */
  --space-6:  24px;
  --space-7:  28px;    /* breaker */
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;
  --space-24: 96px;
  --space-32: 128px;
  --space-40: 160px;

  /* Section padding presets */
  --section-py-tight:    var(--space-20);   /* 80px */
  --section-py-default:  var(--space-24);   /* 96px */
  --section-py-generous: var(--space-32);   /* 128px */
  --section-py-hero:     var(--space-40);   /* 160px */

  /* Container widths */
  --container-prose:  720px;
  --container-main:   1280px;
  --container-wide:   1440px;

  /* Radius system (per-element) */
  --radius-chip:   2px;
  --radius-input:  8px;
  --radius-card:   12px;
  --radius-large:  20px;
  --radius-hero:   40px;
  --radius-pill:   9999px;

  /* Hairlines */
  --hairline: rgba(0, 0, 0, 0.06);
}

/* Layout primitives */
.container {
  max-width: var(--container-main);
  margin-inline: auto;
  padding-inline: clamp(16px, 4vw, 80px);
}

.section {
  padding-block: var(--section-py-default);
}
.section--hero      { padding-block: var(--section-py-hero) var(--space-24); }
.section--generous  { padding-block: var(--section-py-generous); }

/* Typographic rhythm */
h2 { margin-block-end: 40px; }
h3 { margin-block-end: 24px; }
p  { margin-block-end: 20px; }
p:last-child { margin-block-end: 0; }

/* Hairlines */
.divider { height: 1px; background: var(--hairline); border: 0; }
.card    { box-shadow: 0 0 0 1px var(--hairline); }
```

## Application workflow

1. **Choose a radius architecture** — binary / asymmetric / per-element / extreme — and commit to it.
2. **Upgrade section padding** to 80-160px (usually +50% more than you'd default).
3. **Add 20 and 28 to your spacing scale** if you're stuck in strict-4-multiples.
4. **Set explicit `margin-block-end: 40px` on h2** for generous paragraph spacing.
5. **Set container max-width: 1280px** for main content, 720px for prose.
6. **Drop hairlines to 4-10% alpha** (rarely solid gray).
7. **Tighten grid gaps** to 8-24px, but increase section padding to compensate.

## Anti-patterns to kill

- `rounded-md` (8px) on every surface
- `py-12` as the section padding across the whole site
- Strict Tailwind 4-multiple scale with no 20/28/40 interpolation
- `border: 1px solid #e5e7eb` instead of a translucent hairline
- `max-width: 100%` with no container (text blocks flow edge-to-edge at wide)
- Monotone `gap-4` between everything
- Tight 48px section padding that makes the page feel cramped
- Missing paragraph-spacing after h2 (text runs into prose)

## Site references

| Pattern | Case study |
|---|---|
| Binary `0` or `9999px` radius | Duna |
| Asymmetric interlocking radius | Cohere (20px on outer, 0 at joins) |
| Per-element radius variety | Hex, Cal, AngelList |
| Extreme 200-300px radius art plates | AngelList |
| 128-160px section padding | Duna, Incident, Amplemarket |
| 40px margin-block-end on h2 | Cal |
| 20px + 28px in spacing scale | Cal, Duna, Hex |
| Theme-tinted hairlines | Antimetal (navy 4%) |
| 1280px main container | Reducto, Incident, most sites |
| Tight 2-16px component gaps + huge section padding | Reducto |

