Spacing
The 4px spacing scale — one base unit, Tailwind's spacing utilities, and var(--spacing).
Last updated
Spacing is a single base unit stepped on a 4px scale. Tailwind v4 supplies the base
--spacing variable (0.25rem = 4px); every spacing utility (p-4, gap-2, mt-6, w-12)
resolves to calc(var(--spacing) * N). Prefer a scale step; an arbitrary value (p-[3px]) is
for an optical fix the scale cannot express, the way upstream's own components use one.
The scale
Steps compose from the 4px base: 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64px (and beyond). The
SpacingScale bars above are derived live with calc(var(--spacing) * step) — the px figures are
descriptive labels, not separate tokens. There is no per-step token; the scale is the base unit
multiplied by Tailwind's numeric utility name.
| Utility step | Computed | Typical use |
|---|---|---|
1 | 4px | hairline gaps, icon-to-label |
2 | 8px | tight inline spacing |
3 | 12px | control padding |
4 | 16px | default block gap |
6 | 24px | section padding |
8 | 32px | between groups |
12 | 48px | large vertical rhythm |
16 | 64px | page-level separation |
var(--spacing)
--spacing is Tailwind v4's own base unit (0.25rem), not a @vegastack/design-tokens value — the tokens
package ships no --spacing of its own. Internal utilities reference it directly when they need a
spacing-relative length, e.g. the scroll-fade reveal in @vegastack/design-tokens/utilities.css uses
calc(var(--spacing) * 24). In app code, reach for the named utility. If a repeated computed length
is truly needed, name it once (a CSS variable or a utility) rather than repeating the calculation
in every class string:
<div className="gap-2 p-4" />
<div className="px-6" />Tailwind usage
Padding, margin, gap, width/height, inset, and space-* all read the same scale, so a
layout stays on-grid by construction. Prefer gap on flex/grid containers over per-child margins.
<div className="flex flex-col gap-4 p-6">
<header className="flex items-center gap-2" />
<section className="space-y-3" />
</div>Page rhythm
One recipe spaces every product page, on the stock scale above. These are defaults, not tokens: reach for them first, and depart only where a surface has a reason.
| Between | Spacing |
|---|---|
| The page edge and its content (gutters) | --page-gutter — 16px, 24px from sm, 32px from lg (AppShellPage) |
PageHeader and the first content | gap-6 |
| One section and the next | gap-8 |
| A section heading and its content | gap-3 |
| A card's edge and its content | --card-spacing, set by Card's size (default is 16px, sm is 12px) |
| One field and the next | FieldGroup's own gap — do not override it |
| A field's label, control and message | Field's own gap — do not override it |
| A list's toolbar and the table under it | gap-3 |
| Controls in one row | one height tier for the whole row (h-6, h-7, h-8 or h-9) |
<AppShellContent>
<AppShellPage>
<PageHeader title="Tasks" actions={<Button>New task</Button>} />
<div className="flex flex-col gap-8">
<section className="flex flex-col gap-3">
<ListToolbar search={search} filterRow={<FilterRow chips={chips} />} />
<DataList columns={columns} data={tasks} getRowId={(t) => t.id} />
</section>
<section className="flex flex-col gap-3">
<h2 className="font-heading text-base font-medium">Completed</h2>…
</section>
</div>
</AppShellPage>
</AppShellContent>AppShellContent is the scrolling region and carries no padding of its own, so the gutter is
AppShellPage's — see Page layout for the widths and the gutter
scale. A card that holds a section wears its padding through --card-spacing, never a local
p-*, so a size="sm" card tightens every part at once. FieldGroup and Field already space
fields and their parts; an override on one form is the drift this recipe exists to prevent.