Status: Canonical (D1 v1.0)
Last updated: December 19, 2025
Audience: Design Systems, Product Design, Front-End Engineering
Taxonomy placement: D1. Space & Rhythm (part of the 7-dimensional CEM token framework)
Companion specs:
cem-colors) — color weight pairs with spacing rhythmcem-coupling) — normative for interactive operabilitycem-shape) — bend/inset harmony rulescem-layering) — prominent layers “earn” breathing roomcem-stroke) — boundaries, dividers, focus indicatorscem-voice-fonts-typography) — reading rhythm validationcem-timing) — rhythm perceptionAppendices
This document defines a consumer-semantic spacing and layout rhythm token set that:
margin: 16px).dense | normal | sparse) without conflating it with D2
operability.This document is D1 (Space & Rhythm). It intentionally does not define interactive minimums; it consumes D2 coupling minimums as hard constraints.
Developers should apply tokens like:
gap-related, gap-group, gap-section (relationship semantics)inset-control, inset-container (surface semantics)coupling-guard-min (interaction safety contract; defined in D2)…and not value-centric names like space-16 in component code.
A small set of distinct, repeatable space steps should cover most UI. Extended values exist but are intentionally “rare”.
Spacing must preserve:
rem / em and allow platform scaling)We use three layers:
In CEM, spacing/rhythm is not “just layout.” It is a primary subset of Dimensional Tokens (the physical layer: size, distance, bend, stroke, depth).
Use this legend-level dimensional taxonomy to keep token categories coherent:
D1. Space & Rhythm (this document)
D2. Coupling & Compactness (adjacent; constrains D1) — see cem-coupling
D2c. Controls (adjacent; visual geometry constrained by D2) — see cem-controls
D3. Shape & Bend — see cem-shape
D4. Layering — see cem-layering
D5. Stroke & Separation — see cem-stroke
--cem-stroke-none, --cem-stroke-hair, --cem-stroke-standard, --cem-stroke-strong)D6. Typography Thickness — see cem-voice-fonts-typography
D7. Time & Motion Timing — see cem-timing
These rules prevent spacing changes from degrading operability, accessibility, or visual hierarchy:
dense | normal | sparse may adjust gaps/insets/gutters (D1) selectively.When spacing occurs between two interactive affordances, layout must respect D2 guard:
/* If the gap separates adjacent interactive affordances: */
.cem-gap-interactive {
gap: max(var(--cem-gap-related), var(--cem-coupling-guard-min));
}
cem-shape §8.5 for detailed guidance on bend vs inset readability.cem-voice-fonts-typography for typography tokens.The spacing scale uses semantic size names rather than pixel values, making intent clear while preserving M3 rhythm (4dp increments).
Eight steps covering common UI needs:
| Token | Value | Description | tier |
|---|---|---|---|
--cem-dim-xx-small |
0.25rem | 4px — micro gaps, icon padding | required |
--cem-dim-x-small |
0.5rem | 8px — related item gaps, control insets | required |
--cem-dim-small |
0.75rem | 12px — group gaps, inline rhythm | required |
--cem-dim-medium |
1rem | 16px — block gaps, container insets | required |
--cem-dim-large |
1.5rem | 24px — section gaps, surface insets | required |
--cem-dim-x-large |
2rem | 32px — page gaps, wide gutters | required |
--cem-dim-xx-large |
4rem | 64px — extended layout, hero spacing | required |
--cem-dim-xxx-large |
8rem | 128px — maximum breathing room | required |
Cross-reference: These tokens are consumed by D3 Shape (cem-shape) for bend values:
--cem-bend-smooth uses --cem-dim-x-small (8px)--cem-bend-surface uses --cem-dim-small (12px)--cem-bend-modal uses --cem-dim-large + --cem-dim-xx-small (~28px)| Semantic token | Value | M3 dp equivalent | Typical use |
|---|---|---|---|
--cem-dim-xx-small |
0.25rem | 4dp | Micro gaps, tight padding |
--cem-dim-x-small |
0.5rem | 8dp | Related items, control insets |
--cem-dim-small |
0.75rem | 12dp | Group gaps, inline spacing |
--cem-dim-medium |
1rem | 16dp | Block gaps, container insets |
--cem-dim-large |
1.5rem | 24dp | Section gaps, surface padding |
--cem-dim-x-large |
2rem | 32dp | Page gaps, wide gutters |
--cem-dim-xx-large |
4rem | 64dp | Extended layout spacing |
--cem-dim-xxx-large |
8rem | 128dp | Maximum layout spacing |
The taxonomy is organized by what the space means to the user.
Use when arranging siblings.
| Token | Value | Description | tier |
|---|---|---|---|
--cem-gap-related |
var(--cem-dim-x-small) |
Siblings in the same unit (8px) | recommended |
--cem-gap-group |
var(--cem-dim-small) |
Items in the same group, distinct (12px) | recommended |
--cem-gap-block |
var(--cem-dim-medium) |
Between groups/blocks inside one surface (16px) | recommended |
--cem-gap-section |
var(--cem-dim-large) |
Between major sections (24px) | recommended |
--cem-gap-page |
var(--cem-dim-x-large) |
Between page-level regions (32px) | recommended |
Guideline: if users perceive two things as “one unit,” use gap-related. If they perceive “these are separate
things,” move up to gap-group or gap-block.
Important: for interactive adjacency, apply the D2 guard contract (
gap = max(D1 gap, D2 guard)).
Use when padding content within a container.
| Token | Value | Description | tier |
|---|---|---|---|
--cem-inset-control |
var(--cem-dim-x-small) |
Smallest safe inset for tight controls (8px) | recommended |
--cem-inset-container |
var(--cem-dim-medium) |
Default inset for common containers (16px) | recommended |
--cem-inset-surface |
var(--cem-dim-large) |
Comfortable inset for reading surfaces and prominent cards (24px) | recommended |
Cross-reference: When combining insets with bend (D3), ensure inset is large enough to prevent content crowding in
rounded corners. See cem-shape §8.5 for guidance.
These tokens are defined and governed in D2 (cem-coupling). They are listed here as a *
normative constraint*
because they bound how far D1 spacing modes can compress interactive adjacency. Do not set or tune these in D1;
treat them as sourced from the D2 theme.
:root {
/* Minimum distancing between adjacent operable zones (prevents interference). */
--cem-coupling-guard-min: 0.5rem; /* nominally 8px */
/* Minimum operable zone (layout-level). Keep invariant across modes. */
--cem-coupling-zone-min: 3rem; /* nominally 48px @ 16px root */
/* Invisible expansion beyond visuals (halo). */
--cem-coupling-halo: 0.25rem; /* nominally 4px */
/* Legacy aliases (deprecated): keep only while migrating older code */
--cem-touch-separation-min: var(--cem-coupling-guard-min);
--cem-touch-target-min: var(--cem-coupling-zone-min);
}
Interpretation (summary):
This is distinct from UI rhythm.
| Token | Value | Description | tier |
|---|---|---|---|
--cem-rhythm-reading-paragraph |
0.75em | Default paragraph spacing for prose | recommended |
--cem-rhythm-reading-section |
var(--cem-dim-large) |
Space between reading sections within one surface (24px) | recommended |
Cross-reference: Reading rhythm must be validated against typography tokens. See
cem-voice-fonts-typography.
Optimized for scan/compare flows (tables, metrics).
| Token | Value | Description | tier |
|---|---|---|---|
--cem-rhythm-data-row |
var(--cem-dim-x-small) |
Table-like, scan-first row padding (8px) | recommended |
--cem-rhythm-data-group |
var(--cem-dim-medium) |
Scan grouping — subtotals, row groups, metric clusters (16px) | recommended |
These describe layout structures without baking in specific components.
Default vertical stack gap for general UI stacks. Optional tight/loose variants for systems that need explicit range.
Default cluster gap for inline groups (icons + text, toolbars, button clusters). The legacy --cem-layout-inline-*
aliases are deprecated — use --cem-gap-* instead.
These are responsive and should be treated as semantics, not fixed numbers.
| Token | Value | Description | tier |
|---|---|---|---|
--cem-layout-stack-gap |
var(--cem-gap-block) |
Default vertical stack gap for general UI stacks | required |
--cem-layout-cluster-gap |
var(--cem-gap-related) |
Default cluster gap for inline groups | required |
--cem-layout-gutter |
var(--cem-dim-medium) |
Default content gutter (16px) | required |
--cem-layout-gutter-wide |
var(--cem-dim-x-large) |
Wide-screen gutter (32px) | recommended |
--cem-layout-gutter-max |
var(--cem-dim-xx-large) |
Maximum gutter (64px) | recommended |
--cem-layout-stack-tight |
var(--cem-gap-related) |
Tight stack variant — rare, use only when explicitly tighter than default | optional |
--cem-layout-stack-loose |
var(--cem-gap-section) |
Loose stack variant — rare, use only when explicitly looser than default | optional |
--cem-layout-inline-tight |
var(--cem-dim-x-small) |
Deprecated — use --cem-gap-related |
deprecated |
--cem-layout-inline |
var(--cem-dim-small) |
Deprecated — use --cem-gap-group |
deprecated |
--cem-layout-inline-loose |
var(--cem-dim-medium) |
Deprecated — use --cem-gap-block |
deprecated |
Material often frames this as “density.” In CEM, treat this explicitly as a D1 spacing mode that shifts Space & Rhythm while preserving D2 coupling invariants.
:root {
/* dense | normal | sparse */
--cem-layout-spacing: normal;
}
Mode intent summary:
| D1 spacing mode | Primary goal | What changes | What must not change |
|---|---|---|---|
dense |
Increase information density | Step down gaps/insets/gutters ~1 step where safe | D2 coupling invariants (zone/guard), and any gap that separates interactive affordances must still satisfy gap = max(D1 gap, D2 guard) |
normal |
Baseline | Use canonical endpoints as defined | Same invariants |
sparse |
Increase calm/readability | Step up gaps/insets/gutters ~1 step | Same invariants |
Hard constraints (never override):
--cem-coupling-zone-min--cem-coupling-guard-min (may force some “gaps” to stay at or above the minimum)--cem-coupling-halo (must be considered when validating dense clusters)normal mode uses the baseline values defined in §6–7. dense steps gaps down ~1 scale step; sparse steps them up ~1
step. Tokens not in this table are unaffected by spacing mode.
| Token | dense | sparse |
|---|---|---|
--cem-gap-related |
var(--cem-dim-x-small) |
var(--cem-dim-small) |
--cem-gap-group |
var(--cem-dim-x-small) |
var(--cem-dim-medium) |
--cem-gap-block |
var(--cem-dim-small) |
var(--cem-dim-large) |
--cem-gap-section |
var(--cem-dim-medium) |
var(--cem-dim-x-large) |
--cem-gap-page |
var(--cem-dim-large) |
var(--cem-dim-xx-large) |
--cem-inset-control |
var(--cem-dim-x-small) |
var(--cem-dim-small) |
--cem-inset-container |
var(--cem-dim-small) |
var(--cem-dim-large) |
--cem-inset-surface |
var(--cem-dim-medium) |
var(--cem-dim-x-large) |
--cem-layout-gutter |
var(--cem-dim-small) |
var(--cem-dim-large) |
--cem-layout-gutter-wide |
var(--cem-dim-large) |
var(--cem-dim-xx-large) |
--cem-layout-gutter-max |
var(--cem-dim-x-large) |
var(--cem-dim-xxx-large) |
--cem-rhythm-reading-paragraph |
0.6em | 1em |
--cem-rhythm-reading-section |
var(--cem-dim-medium) |
var(--cem-dim-x-large) |
--cem-rhythm-data-row |
var(--cem-dim-xx-small) |
var(--cem-dim-small) |
--cem-rhythm-data-group |
var(--cem-dim-small) |
var(--cem-dim-large) |
Components should bind to semantic endpoints (gap-*, inset-*, rhythm-*) and inherit spacing mode automatically.
If a component is interactive and uses layout gaps between peer affordances, resolve its gap as:
gap = max(D1 semantic gap, D2 coupling guard).
If a component must remain stable across spacing modes (rare), bind it to reference steps explicitly and document why.
Use these patterns so teams can implement screens without inventing spacing semantics.
Stacks (vertical composition)
--cem-layout-stack-gap (maps to --cem-gap-block).--cem-gap-related.Clusters (horizontal composition)
--cem-layout-cluster-gap (maps to --cem-gap-related).gap: max(var(--cem-layout-cluster-gap), var(--cem-coupling-guard-min)).Cards / panels / surfaces
--cem-inset-container.--cem-inset-surface.--cem-inset-control (only when content remains legible).cem-shape §8.5.Sections and page regions
--cem-gap-block.--cem-gap-section.--cem-gap-page.Gutters (responsive framing)
--cem-layout-gutter.--cem-layout-gutter-wide.--cem-layout-gutter-max.Reading and data cadence
--cem-rhythm-reading-paragraph and --cem-rhythm-reading-section.--cem-rhythm-data-row and --cem-rhythm-data-group.--cem-dim-*) as the foundation.gap-*, inset-*, rhythm-*) to dimension tokens.zone/guard/halo) as hard constraints for interactive adjacency — see
cem-coupling.data-cem-spacing="dense|normal|sparse" only if product needs spacing modes.cem-shape §8.5.This excerpt is provided for convenience when working in D1. The canonical safety definitions and governance live in
D2. Coupling & Compactness (cem-coupling); visual geometry values live in D2c.
Controls (cem-controls).
| Coupling mode | Product intent | D2c Controls visual geometry trend | D2 halo (--cem-coupling-halo) trend |
Typical surfaces |
|---|---|---|---|---|
forgiving |
Minimize mis-coupling for imprecise input | Larger controls/rows; more internal padding | Smaller (visuals already meet zone) | mobile-first, kiosks, accessibility-first, gaze/dwell |
balanced |
Default across modalities | Baseline control heights/rows | Baseline | mixed pointer + touch, general app UI |
compact |
Increase information density without breaking operability | Smaller visuals; reduced chrome | Larger (use halo to preserve zone) | data grids, admin tools, scan-heavy panels |
Normative invariants (do not override in D1):
--cem-coupling-zone-min is mode-invariant.--cem-coupling-guard-min is mode-invariant.gap = max(D1 semantic gap, D2 coupling guard).This D1 spec is a contract. Changes must be intentional, reviewable, and versioned.
Treat as major (breaking) if you:
gap-*, inset-*, layout-*, rhythm-*).gap-group stops meaning “within a group”).Treat as minor/patch if you:
D1 is subordinate to D2 for operability. The following must hold in every release:
gap = max(D1 semantic gap, D2 coupling guard).Interactive-adjacency contract (non-normative for generator): any consumer using D1 gaps between interactive affordances must resolve the effective gap as:
gap:
max
(
var
(
--cem-gap-related
)
,
var
(
--cem-coupling-guard-min
)
)
;
The generator does not enforce this — it is component-author responsibility, documented in the manifest notes above.
Reading-rhythm validation is deferred to D6 cross-check (Phase 12 in the implementation plan). D1 cannot validate rhythm in isolation.
Token tier is encoded in the tier column of each source table. The manifest validator derives expected token names
from these tables using the same XPath pattern as cem-dimension.html.
| Source table h6 id | Tokens covered | Validator derivation |
|---|---|---|
cem-dim-scale |
--cem-dim-* (8 tokens) |
one token per row |
cem-dim-gaps |
--cem-gap-* (5 tokens) |
one token per row |
cem-dim-insets |
--cem-inset-* (3 tokens) |
one token per row |
cem-dim-rhythm-reading |
--cem-rhythm-reading-* (2 tokens) |
one token per row |
cem-dim-rhythm-data |
--cem-rhythm-data-* (2 tokens) |
one token per row |
cem-dim-layout |
--cem-layout-* (10 tokens; deprecated tier omitted from default output) |
one token per row |
Spacing mode overrides (cem-dim-spacing-overrides) produce no new token names — they override existing tokens within
:root[data-cem-spacing="dense"] and :root[data-cem-spacing="sparse"] selectors.
Local CEM documentation