Skip to content
Component installs need the registry setup
VegaStack Design

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.

1 · calc(var(--spacing) * 1) · 4px
2 · calc(var(--spacing) * 2) · 8px
3 · calc(var(--spacing) * 3) · 12px
4 · calc(var(--spacing) * 4) · 16px
6 · calc(var(--spacing) * 6) · 24px
8 · calc(var(--spacing) * 8) · 32px
12 · calc(var(--spacing) * 12) · 48px
16 · calc(var(--spacing) * 16) · 64px

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 stepComputedTypical use
14pxhairline gaps, icon-to-label
28pxtight inline spacing
312pxcontrol padding
416pxdefault block gap
624pxsection padding
832pxbetween groups
1248pxlarge vertical rhythm
1664pxpage-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>
Do
Use scale steps: p-4, gap-2, mt-6 — multiples of the 4px base.
Don't
Reach for off-grid lengths as layout: p-[15px], gap-[7px], mt-[18px]. Keep arbitrary values for optical fixes — a 1px nudge to centre a glyph, a 3px inset the scale has no step for.

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.

BetweenSpacing
The page edge and its content (gutters)--page-gutter — 16px, 24px from sm, 32px from lg (AppShellPage)
PageHeader and the first contentgap-6
One section and the nextgap-8
A section heading and its contentgap-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 nextFieldGroup's own gap — do not override it
A field's label, control and messageField's own gap — do not override it
A list's toolbar and the table under itgap-3
Controls in one rowone 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.

  • Radius — the corner-rounding ramp, derived from one --radius, that pairs with spacing on every surface.
  • Card and Separator — layout primitives that consume the spacing scale for their internal padding and rhythm.

On this page