| name | spatial-rhythm-and-whitespace |
| description | 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." |
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):
.card { border-radius: 0; }
.btn { border-radius: 9999px; }
This is the most aggressive and recognizable move.
(B) Cohere's asymmetric interlocking — 20px on outer corners, 0 at joins:
.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; }
.panel-top { border-radius: 20px 20px 0 0; }
.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:
.btn-pill { border-radius: 9999px; }
.btn-chip { border-radius: 30px; }
.card { border-radius: 12px; }
.card-large { border-radius: 20px; }
.input { border-radius: 8px; }
.avatar { border-radius: 50%; }
.tag { border-radius: 2px; }
.feature { border-radius: 40px; }
(D) Extreme-radius for art (AngelList) — 200–300px radius creates "stadium" shapes:
.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:
.section { padding-block: 80px; }
.section { padding-block: 120px; }
.section-hero { padding-block: 140px 96px; }
.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.
h2 { margin-block-end: 40px; }
h3 { margin-block-end: 24px; }
p + p { margin-block-start: 1.25em; }
Framer's paragraphSpacing pattern (from Cal.com and Duna):
h2 { padding-block-end: 40px; }
p { padding-block-end: 20px; }
Rule 5: Container widths are deliberate, and 1200-1280px is the sweet spot
.container { max-width: 1280px; margin-inline: auto; padding-inline: 24px; }
.container-reducto { max-width: 1280px; }
.container-prose { max-width: 720px; }
.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:
.container {
padding-inline: clamp(16px, 4vw, 80px);
}
Rule 6: Hairline dividers at 4-11% alpha, never solid
Premium sites use hairlines with LOW opacity:
--hairline-subtle: rgba(0, 0, 0, 0.04);
--hairline-light: rgba(0, 0, 0, 0.06);
--hairline-medium: rgba(0, 0, 0, 0.10);
--hairline-navy: rgba(12, 38, 77, 0.04);
--hairline-warm: rgba(0, 45, 51, 0.10);
hr { border: 0; border-top: 1px solid var(--hairline-light); }
.card { box-shadow: 0 0 0 1px var(--hairline-light); }
.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:
.grid-editorial { gap: 12px; }
.grid-cards { gap: 24px; }
.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
:root {
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-7: 28px;
--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-py-tight: var(--space-20);
--section-py-default: var(--space-24);
--section-py-generous: var(--space-32);
--section-py-hero: var(--space-40);
--container-prose: 720px;
--container-main: 1280px;
--container-wide: 1440px;
--radius-chip: 2px;
--radius-input: 8px;
--radius-card: 12px;
--radius-large: 20px;
--radius-hero: 40px;
--radius-pill: 9999px;
--hairline: rgba(0, 0, 0, 0.06);
}
.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); }
h2 { margin-block-end: 40px; }
h3 { margin-block-end: 24px; }
p { margin-block-end: 20px; }
p:last-child { margin-block-end: 0; }
.divider { height: 1px; background: var(--hairline); border: 0; }
.card { box-shadow: 0 0 0 1px var(--hairline); }
Application workflow
- Choose a radius architecture — binary / asymmetric / per-element / extreme — and commit to it.
- Upgrade section padding to 80-160px (usually +50% more than you'd default).
- Add 20 and 28 to your spacing scale if you're stuck in strict-4-multiples.
- Set explicit
margin-block-end: 40px on h2 for generous paragraph spacing.
- Set container max-width: 1280px for main content, 720px for prose.
- Drop hairlines to 4-10% alpha (rarely solid gray).
- 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 |