Skip to main content

Spacing

Spacing tokens define the repeatable distances used to build rhythm, hierarchy, alignment, and ergonomics across interfaces.

This system is built on two explicit layers:

  1. Core Tokens — intent-free primitives and the single responsiveness engine
  2. Semantic Tokens — stable layout patterns consumed by UI code

Components must always consume semantic spacing, never core spacing directly.

Rule: Core spacing is never referenced in components.


Core Tokens

Core spacing tokens are intent-free primitives and the single source of truth for responsiveness.

They exist to:

  • centralize fluid logic (the responsive engine lives here)
  • keep semantic tokens as aliases (stable names, theme-tunable engine)
  • make the system predictable (no ad-hoc coefficients in semantics)

Core set

  • Primitive: space.unit
  • Steps: space.0, space.1, space.2, space.3, space.4, space.6, space.8, space.12, space.16

Keep the step set small. If you want space.5, you likely need a semantic mapping, not a new core step.

Example

const core = {
space: {
/**
* The Responsive Engine
* Container-first: scales with the inline size of the nearest query container.
* If no eligible container exists, cqi falls back to the small viewport unit for that axis (sv*).
*/
unit: 'clamp(4px, 0.5cqi + 2px, 8px)',

/** Core steps (sparse scale) */
0: '0px',
1: 'calc(1 * {space.unit})',
2: 'calc(2 * {space.unit})',
3: 'calc(3 * {space.unit})',
4: 'calc(4 * {space.unit})',
6: 'calc(6 * {space.unit})',
8: 'calc(8 * {space.unit})',
12: 'calc(12 * {space.unit})',
16: 'calc(16 * {space.unit})',

/** Tier-2 (optional, not default): container-aware unit (kept for explicitness) */
unitCq: 'clamp(4px, 0.6cqi, 8px)',
},
};

Expected consumption pattern: semantic tokens reference core tokens by alias.


Semantic Tokens

Semantic spacing is anchored in layout physics, not UX categories.

Token structure

{pattern}.{context}.{step?}
  • pattern: inset, gap, gutter, separation
  • context: control, surface, stack, inline, page, section, interactive
  • step: xs, sm, md, lg, xl, min

step is only used in some cases.

Patterns:

{pattern}Description
insetpadding inside elements
gapspacing between siblings
gutterstructural layout padding (page/section)
separationminimum ergonomic distance between interactive targets

Canonical shapes

  • inset.control.{sm|md|lg}
  • inset.surface.{sm|md|lg}
  • gap.stack.{xs|sm|md|lg|xl}
  • gap.inline.{xs|sm|md|lg}
  • gutter.{page|section}
  • separation.interactive.min

Semantic Tokens Summary Table

tokenuse when you are building…contract (must be true)default mapping
inset.control.smcompact controlsinternal padding core.space.2
inset.control.mddefault controlsinternal padding core.space.3
inset.control.lglarge/prominent controlsinternal padding core.space.4
inset.surface.smtight surfacesinternal padding core.space.3
inset.surface.mddefault surfacesinternal padding core.space.4
inset.surface.lgspacious surfacesinternal paddingcore.space.6
gap.stack.xstight vertical rhythmsibling spacing via gapcore.space.2
gap.stack.smmedium vertical rhythmsibling spacing via gapcore.space.3
gap.stack.mddefault vertical rhythmsibling spacing via gapcore.space.4
gap.stack.lgroomy vertical rhythmsibling spacing via gapcore.space.6
gap.stack.xlsection-level rhythmsibling spacing via gapcore.space.8
gap.inline.xsvisual-only tight grouping (icon + label)never between interactive targetscore.space.1
gap.inline.sminline groupingalias of gap.stack.xscore.space.2
gap.inline.mdlooser inline groupingalias of gap.stack.smcore.space.3
gap.inline.lgspacious inline groupingalias of gap.stack.mdcore.space.4
gutter.pagepage outer paddingbounded, structuralclamp(core.space.4, core.space.6, core.space.12)
gutter.sectionsection outer paddingbounded, structuralclamp(core.space.3, core.space.4, core.space.12)
separation.interactive.mindense interactive target clustersonly between click/tap/focusable targetsclamp(8px, core.space.2, 12px)

Example

const spacing = {
inset: {
control: {
sm: 'space.2',
md: 'space.3',
lg: 'space.4',
},
surface: {
sm: 'space.3',
md: 'space.4',
lg: 'space.6',
},
},

gap: {
stack: {
xs: 'space.2',
sm: 'space.3',
md: 'space.4',
lg: 'space.6',
xl: 'space.8',
},
inline: {
xs: 'space.1', // visual-only tight grouping
sm: 'gap.stack.xs', // aliases reduce knobs
md: 'gap.stack.sm',
lg: 'gap.stack.md',
},
},

gutter: {
page: 'clamp({space.4}, {space.6}, {space.12})',
section: 'clamp({space.3}, {space.4}, {space.12})',
},

separation: {
interactive: {
min: 'clamp(8px, {space.2}, 12px)',
},
},
};

Rules of Engagement (non-negotiable)

  1. Semantic-only consumption: components use semantic spacing only.
  2. Gap-first: sibling spacing uses gap (Flex/Grid) by default.
  3. Inset is for padding: spacing.inset.* is only for internal padding.
  4. Gutters are structural: use spacing.gutter.* for page/section layout padding.
  5. Separation is ergonomic: spacing.separation.interactive.min is only for interactive targets.
  6. No responsive logic in components: responsiveness lives in Core (core.space.unit), not in UI code.

Decision Matrix (pick fast)

  1. Padding inside an element?spacing.inset.control.* / spacing.inset.surface.*
  2. Spacing between siblings?spacing.gap.stack.* / spacing.gap.inline.*
  3. Page/section structure?spacing.gutter.page / spacing.gutter.section
  4. Dense cluster of interactive targets?spacing.separation.interactive.min

Usage Examples

UsageToken
Stack (vertical rhythm){gap: spacing.gap.stack.md}
Inline group (visual grouping){gap: spacing.gap.inline.sm}
Surface padding{padding: spacing.inset.surface.md}
Page gutter{padding-inline: spacing.gutter.page}
Dense toolbar (interactive targets){gap: spacing.separation.interactive.min}

Build output may expose semantic tokens as CSS variables (as shown above) or as framework-specific bindings. The semantic names remain the API.


Compatibility: Flex gap fallback (only if required)

If your support matrix includes environments without flex-gap support, emit a fallback:

const rowStyles = {
display: 'flex',
flexDirection: 'row',
/* Preferred */
gap: spacing.gap.inline.sm,

/* Fallback */
'@supports not (gap: 1rem)': {
'& > * + *': {
marginLeft: spacing.gap.inline.sm,
},
},
};

Theming & Density (allowed knobs)

Themes may tune spacing without renaming semantic tokens.

  1. Tune core.space.unit (global density + responsiveness)

    • denser: lower clamp bounds
    • airier: higher clamp bounds
  2. Optional density mode (rare) If multiple UI densities are truly needed, remap only:

    • spacing.inset.control.*
    • spacing.gap.* (stack + inline aliases)

Keep spacing.gutter.* and spacing.separation.* conservative.


Tier-2 Capability: Container-Aware Spacing (optional)

For highly modular layouts (cards in grids, split panes), you may introduce:

  • core.space.unitCq = clamp(4px, 0.6cqi, 8px)

This is not default. Use it only in layout primitives/surfaces explicitly designed for container scaling.


Summary

  • One responsiveness engine: core.space.unit
  • Small core step set: core.space.{0..16} (sparse)
  • Small semantic set: inset / gap / gutter / separation
  • Gap-first, semantic-only consumption
  • Responsive by contract, no breakpoint logic in components