Overview
Every spacing decision in Flatpay rounds to 4 px. The grid is fine enough to handle dense data tables and coarse enough that two designers picking values independently land in the same place.
We use Tailwind's default spacing scale verbatim — no semantic alias layer, no custom token names, no fork. The scale is good. Reach for p-*, gap-*, space-*, m-* directly. A semantic name would only obscure what the value is.
The first principle
Vary spacing for hierarchy. Equal padding everywhere reads as flat. Tighter groupings + generous separations create rhythm — and rhythm makes a layout scannable in a glance.
The scale
The working subset of Tailwind's scale, with the use cases that pull each step into reach. The full scale runs higher (32rem, 96rem); rows beyond spacing.24 are valid but rarely the right answer in product UI.
spacing.0p-0 · gap-000spacing.0.5p-0.5 · gap-0.50.125rem2spacing.1p-1 · gap-10.25rem4spacing.1.5p-1.5 · gap-1.50.375rem6spacing.2p-2 · gap-20.5rem8spacing.2.5p-2.5 · gap-2.50.625rem10spacing.3p-3 · gap-30.75rem12spacing.4p-4 · gap-41rem16spacing.5p-5 · gap-51.25rem20spacing.6p-6 · gap-61.5rem24spacing.8p-8 · gap-82rem32spacing.10p-10 · gap-102.5rem40spacing.12p-12 · gap-123rem48spacing.16p-16 · gap-164rem64spacing.20p-20 · gap-205rem80spacing.24p-24 · gap-246rem96
Below 4 px is for hairlines, not gaps
spacing.0.5 (2 px) and spacing.px(1 px) exist for borders, focus rings, and optical alignment of glyphs — not for general layout. If you're reaching for them between two siblings, you're probably at the wrong density tier.
Padding, gap, margin
Three properties, three different jobs. Most of the time, you want gap: it eliminates margin collapse, scales cleanly with flex/grid, and survives reordering without the first/last-child cleanup that margin needs.
Padding
Inside a container
Space between a container's edge and its contents. Reach for it on cards, buttons, inputs.
Gap
Between siblings
Space between elements inside a flex or grid parent. The default — use it instead of margins.
Margin
Outside an element
Push space around an element from the outside. Use sparingly — gap covers most cases without margin collapse.
Default to gap
Margins exist for the rare cases where flex/grid isn't available — long prose blocks, isolated standalone elements. If you're inside a flex or grid parent, gap is always the answer.
Density
Three tiers cover almost every surface in the system. Pick deliberately — density is a design decision, not a styling one. The same component at different densities tells the user something different about the page.
p-3 · gap-1
Tables, list rows, dense dashboards. Reach for it when an analyst needs to see more at once.
p-5 · gap-2
The everyday card, the form section, the modal body. The starting point for anything new.
p-8 · gap-3
Hero metrics, empty states, marketing surfaces. Reach for it when restraint earns the room.
Page rhythm
Pages have a rhythm too — vertical breaks that signal "new chapter" versus "same idea continues." Three values cover almost every docs and marketing page. Hold them steady.
- Hero → first section: 64 px. The biggest break on the page; readers feel it as a chapter end.
- Section → section: 64 px. Same value to keep the rhythm honest.
- Last section → footer: 96 px. One step larger so the page ends, not just stops.
In code
Reach for Tailwind utilities directly. The class is the token; no extra indirection.
tsx
// Card — default density, gap between header and body
<article className="flex flex-col gap-2 rounded-lg border bg-card p-5">
<header className="flex items-center justify-between">
<h3 className="text-sm font-semibold">Lucky Garden</h3>
<span className="eyebrow text-muted-foreground">Demo-ready</span>
</header>
<p className="text-sm text-muted-foreground">Onboarding flow for new merchants.</p>
</article>
// Form section — vertical rhythm between fields
<form className="flex flex-col gap-6">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium">Name</label>
<input className="rounded border px-3 py-2" />
</div>
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium">Risk</label>
<select className="rounded border px-3 py-2" />
</div>
</form>
// Page section — top padding sets the rhythm
<section className="pt-16">
<h2 className="text-2xl font-semibold tracking-tight">…</h2>
</section>Don'ts
- Don't pad everything equally. A heading wants room above it, a list of related items wants room between them — not the same gap. Vary spacing for hierarchy.
- Don't reach off-grid.If a value isn't in the scale, the layout is asking for a different answer.
p-[15px]is a symptom; fix the layout, not the number. - Don't nest cards inside cards. Two layers of border + padding looks like a debug outline. Flatten the hierarchy or use rules and gap to separate.
- Don't reach for margin first.
gapis the default in flex and grid. Margin is the fallback for the few places those don't fit. - Don't cap density to look roomy.Tables and dashboards exist to surface signal. Compact density isn't harsh — it's a respect for the operator's time.