Spacing
Spacing tokens set the padding and gaps in Nuton components and layouts. Use them for every inset, gap and offset, so screens keep one rhythm.
Beta because every space-* token (16) is interim: Figma binds almost no spacing (0.2% of gaps), so the scale is the set of values observed on Nuton screens. Stable needs a Figma Spacing collection (D9 backlog, D20) and the P1 padding update in Figma (FC-08). There are no code-only spacing tokens.
Overview
One scale covers component insets, the gaps between elements and the space around sections. It's built on 4px: space-N is N × 4px, so the names match Tailwind's default spacing numbers.
00px
0-52px
14px
1-56px
28px
2-510px
312px
3-514px
416px
520px
624px
832px
1040px
1248px
1664px
2080px
- Whole steps (0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20) are the layout scale: gaps between components, card and panel padding, space between sections.
- Half steps (0-5, 1-5, 2-5, 3-5) add 2px increments below 16px. Use them inside components, where a 4px jump is too coarse for optical balance: button insets, icon gaps, focus offsets.
- The scale stops at 80px. Larger distances are layout decisions (grid, breakpoints), not spacing tokens.
Interim: spacing has no Figma home yet
Under D20, design tokens are authored in Figma. Spacing is the exception for now: Figma binds almost no gaps or padding, so these values come from what Nuton screens actually use (docs/token-architecture.md, Spacing proposal). Every token is flagged nuton.interim and the token check warns about it on every run. When the DS Owner adds a Figma Spacing collection (D9 backlog), the tokens move into the Figma snapshot and the names stay the same.
Tokens
Every spacing token in @nuton/tokens, in generated order: whole steps first, then half steps. The bar shows the value at full size.
| Preview | Token | Value | Source |
|---|---|---|---|
--nu-space-0 | 0px | Interim | |
--nu-space-1 | 4px | Interim | |
--nu-space-2 | 8px | Interim | |
--nu-space-3 | 12px | Interim | |
--nu-space-4 | 16px | Interim | |
--nu-space-5 | 20px | Interim | |
--nu-space-6 | 24px | Interim | |
--nu-space-8 | 32px | Interim | |
--nu-space-10 | 40px | Interim | |
--nu-space-12 | 48px | Interim | |
--nu-space-16 | 64px | Interim | |
--nu-space-20 | 80px | Interim | |
--nu-space-0-5 | 2px | Interim | |
--nu-space-1-5 | 6px | Interim | |
--nu-space-2-5 | 10px | Interim | |
--nu-space-3-5 | 14px | Interim |
Source: packages/tokens/src/interim/radius-spacing.json. All values are global tokens; there are no semantic spacing aliases yet. Components wrap them in their own variables, such as --nu-button-pad-inline.
Usage
CSS variables
Load @nuton/tokens/tokens.css once, then reference the variables in any property that takes a length.
.lesson-card {
display: grid;
gap: var(--nu-space-3);
padding: var(--nu-space-5);
}
/* Subtract a 1px border so the outer size stays on the scale (as Input does) */
.space-chip {
border: 1px solid var(--nu-stroke-soft-200);
padding-inline: calc(var(--nu-space-3) - 1px);
}Tailwind
The Tailwind v4 theme and v3 preset in @nuton/tokens expose semantic colours, the type scale, radius, shadows, easing and breakpoints. They don't generate spacing utilities. Reference the variables with Tailwind's arbitrary-value syntax instead. Tailwind's built-in spacing numbers line up with the token names, but they aren't bound to the tokens, so they won't follow a future change to the scale.
<div className="grid gap-(--nu-space-3) p-(--nu-space-5)">
…
</div><div className="grid gap-[var(--nu-space-3)] p-[var(--nu-space-5)]">
…
</div>Inside @nuton/ui, a lint rule blocks arbitrary Tailwind values: components set spacing in their CSS through --nu-space-* and their own component variables.
Choosing a token
- Inside a control, use the component's variables (
--nu-button-pad-inline,--nu-input-pad-inline). Override them on a wrapper only for a documented exception. - Related items sit closer than unrelated ones. Field parts are
space-1(4px) apart; actions in a row usespace-2(8px); cards in a grid usespace-3orspace-4. - Insets grow with the control. Bigger controls get bigger insets, and an icon edge sits closer to the border than a text edge (P1).
- Stay on the scale. If a value isn't a token, round to the nearest step. When a border would push a size off the scale, subtract it with
calc().
In use
Spacing decisions in real components. P1, approved at the owner's request, is the clearest example: the padding on buttons and inputs was rebalanced so insets scale with size, while every height stayed the same.
Button insets
sm · 10px icon edge · 14px text edgemd · 12px icon edge · 16px text edgelg · 16px icon edge · 20px text edge<Button size="sm" leadingIcon={<RiAddLine />}>New lesson</Button>
<Button size="md" leadingIcon={<RiAddLine />}>New lesson</Button>
<Button size="lg" leadingIcon={<RiAddLine />}>New lesson</Button>| Size | Height | Block inset | Text-edge inset | Icon-edge inset | Icon ↔ label gap |
|---|---|---|---|---|---|
sm | 36 | space-2 · 8px | space-3-5 · 14px | space-2-5 · 10px | space-1 · 4px |
md | 44 | space-3 · 12px | space-4 · 16px | space-3 · 12px | space-1-5 · 6px |
lg | 52 | space-4 · 16px | space-5 · 20px | space-4 · 16px | space-2 · 8px |
Heights are measured in the browser (see Button). Figma uses one fixed text inset at every size, so large buttons looked cramped and small ones loose; P1 replaced it with the steps above. Figma still shows the old padding (FC-08).
Input insets
<Field label="Search lessons">
<Input leadingIcon={<RiSearchLine />} placeholder="Photosynthesis" />
</Field>
<Field label="Search lessons, small">
<Input size="sm" leadingIcon={<RiSearchLine />} placeholder="Photosynthesis" />
</Field>| Part | Token | Note |
|---|---|---|
| Inline padding, large (48) | space-4 · 16px | P1. Figma uses a larger inset at both sizes. |
| Inline padding, small (36) and inline appearance | space-3 · 12px | P1. The 1px border is subtracted, so the content edge lands on the token. |
| Icon ↔ text gap | space-2 · 8px | Unchanged from Figma. |
| Label ↔ control ↔ hint | space-1 · 4px | P1 amended: follows Figma again. |
| Label row height | space-6 · 24px | Figma Label 4446:59279. |
Gap between actions
<div className="actions"> {/* gap: var(--nu-space-2) */}
<Button size="sm" appearance="stroke">Save draft</Button>
<Button size="sm" trailingIcon={<RiArrowRightLine />}>Publish lesson</Button>
</div>Accessibility
Spacing decides how big a target is and how close it sits to the next one. WCAG 2.2 AA (2.5.8 Target size, minimum) asks for targets of at least 24 × 24 CSS pixels.
A smaller target still passes if a 24px circle centred on it doesn't overlap another target or that target's circle. So when a control can't grow, the gap around it has to. space-6 (24px) is the minimum target size, which makes it a useful check: a control plus its share of the surrounding gap should reach it.
| Component | Smallest target (measured) | 2.5.8 result |
|---|---|---|
Button sm, icon-only | 36 × 36 | Passes |
Input sm and inline | 36 tall | Passes |
Button group 2xs, icon-only | 22 × 22, edge to edge | Fails (axe target-size, C26) |
Link button md / sm | 20 / 16 tall, no padding | Passes only inline in a sentence or with enough space around it (C28) |
Open: two components need a spacing decision (C26, C28)
C26: 2xs icon-only Button group items need either larger targets or a gap between them. C28: Link button has no padding, so its target depends on the space you leave around it. Both wait on a design decision; until then, don't use 2xs for icon-only groups, and don't place link buttons side by side without a gap.
- Spacing tokens are in px. They scale with browser zoom, which is what WCAG 1.4.4 and 1.4.10 test.
- Space is never the only grouping cue for meaning: a field's label and hint are tied to the control with
aria-labelledbyandaria-describedby, not just placed nearby.
You must: keep every target at 24 × 24 or give it enough space around it; check new components with axe's target-size rule.
Guidelines and related
Use spacing tokens for
- Padding, gaps and margins in product screens and components.
- Half steps inside components, where a control needs optical balance.
- Making room around small targets so they meet 24 × 24.
Don't use them for
- Component heights. Those are fixed per size (Button 36/44/52, Input 48/36).
- Page-level layout widths. Use the layout and breakpoint rules.
- Overriding a component's padding to make it fit. Pick another size instead.