Status: Canonical (D1 v1.0)
Last updated: August 10, 2026
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.md) — color weight pairs with spacing rhythmcem-coupling.md) — normative for interactive operabilitycem-shape.md) — bend/inset harmony rulescem-layering.md) — prominent layers "earn" breathing roomcem-stroke.md) — boundaries, dividers, focus indicatorscem-voice-fonts-typography.md) — reading rhythm validationcem-timing.md) — 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.md
D2c. Controls (adjacent; visual geometry constrained by D2) — see cem-controls.md
D3. Shape & Bend — see cem-shape.md
D4. Layering — see cem-layering.md
D5. Stroke & Separation — see cem-stroke.md
--cem-stroke-none, --cem-stroke-hair, --cem-stroke-standard, --cem-stroke-strong)D6. Typography Thickness — see cem-voice-fonts-typography.md
D7. Time & Motion Timing — see cem-timing.md
These rules prevent spacing changes from degrading operability, accessibility, or visual hierarchy:
dense | normal | sparse may adjust gaps/insets/gutters (D1) selectively.The explicit density policies in D2 §6.0 are independently opted in and have their own target/guard values. Compact is gated to precise input; explicitly authored super-compact is content-sized on every input modality. Neither changes the canonical spacing mode values or the normal/default coupling contract.
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.md §8.5 for detailed guidance on bend vs inset readability.cem-voice-fonts-typography.md 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.md) 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 |
--cem-menu-inset | max(var(--cem-inset-control), var(--cem-coupling-guard-min)) | Menu surface clearance around outer item indicators; retained in compressed density | required |
--cem-menu-item-gap | max(var(--cem-gap-related), calc(2 * var(--cem-coupling-guard-min))) | Menu item separation reserves room for simultaneous selected and focused indicators | required |
Menu inset and item-gap endpoints express preferred spacing independently of
control padding. Super-compact may reduce those preferences to zero/related
spacing, while the menu layout still reserves the actual outward indicator
extent from D0 stripe thickness and D5 focus width/offset. The current three-stripe
ring requires at least 3 × --cem-zebra-strip-size at surface edges and twice
that between adjacent items. Focus outlines can require a larger floor.
Compute this bound where the menu lays out its children so inherited stripe
and stroke overrides remain effective below an ancestor density boundary.
Density never removes this paint clearance or increases generic button padding.
Cross-reference: When combining insets with bend (D3), ensure inset is large enough to prevent content crowding in
rounded corners. See cem-shape.md §8.5 for guidance.
These tokens are defined and governed in D2 (cem-coupling.md). 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.md.
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 |
--cem-data-row-padding-block | var(--cem-control-padding-y) | Structured row block inset; shared by trees and tabular views | required |
--cem-data-row-padding-inline | var(--cem-control-padding-x) | Structured row inline inset; hierarchy indentation is separate | required |
--cem-data-row-gap | var(--cem-gap-related) | Vertical spacing between structured rows and groups | required |
--cem-data-tree-padding-factor | 0.5 | Tree row padding multiplier applied to inherited control padding; sparse uses full padding | required |
--cem-data-tree-indent | var(--cem-control-padding-x) | Hierarchy indentation independent of row padding | required |
--cem-data-tree-padding-inline | 0px | Outer tree inset, independent of per-level indentation | required |
--cem-data-tree-column-gap | var(--cem-gap-related) | Horizontal separation of tree disclosure, icon and label | required |
data-cem-density="compact" is an inherited desktop chrome recipe. Use
data-cem-density="super-compact" on a page/container alongside data-theme
for content-sized application chrome and structured trees/tabular data. Controls
have zero padding; structured rows follow adjacent lines of one paragraph,
with no extra padding or vertical gap.
Structured rows retain a 1lh minimum; generic buttons have no imposed height
minimum, so content, wrapped labels and user text scaling can grow naturally.
Icons remain 1em; the profile never scales the font to manufacture density.
Tree geometry distinguishes its 0.25em outer inline inset, 0.25em horizontal
column gaps and 1.4em increment per nesting level. These follow the row font;
they do not introduce row padding or vertical spacing between paragraph lines.
Compact requires a fine primary pointer with hover and no coarse pointer at all. Super-compact is an explicit geometry choice on every input modality; coarse, unknown and hybrid input do not reinstate its padding or insets. These profiles are separate from existing spacing/coupling modes, which retain their previous values. D2 owns the requested target policy; D2c owns row/control geometry and D5 keeps structured-row focus inside its line. Components consume these tokens without per-component density recipes.
data-cem-density="normal" establishes a nested baseline boundary by reading
the original canonical values for the density table's endpoints. It restores
insets, gutters and data-row geometry without redefining typography or unrelated
spacing tokens. Omission keeps the ancestor's density. data-cem-spacing remains
an independent spacing choice; use data-cem-density="normal" together with
data-cem-spacing="sparse" to show a sparse sample inside a condensed page.
| Token | compact | super-compact |
|---|---|---|
--cem-gap-related | 0.25rem | 0.25rem |
--cem-gap-group | 0.5rem | 0.5rem |
--cem-gap-block | 0.5rem | 0.5rem |
--cem-gap-section | 0.75rem | 0.75rem |
--cem-inset-control | 0.25rem | 0px |
--cem-menu-inset | max(var(--cem-inset-control), var(--cem-coupling-guard-min)) | 0px |
--cem-menu-item-gap | max(var(--cem-gap-related), calc(2 * var(--cem-coupling-guard-min))) | var(--cem-gap-related) |
--cem-inset-container | 0.5rem | 0px |
--cem-inset-surface | 0.75rem | 0px |
--cem-layout-gutter | 0.5rem | 0px |
--cem-data-row-padding-block | var(--cem-control-padding-y) | 0px |
--cem-data-row-padding-inline | var(--cem-control-padding-x) | 0px |
--cem-data-row-gap | var(--cem-gap-related) | 0px |
--cem-data-tree-indent | 1.4em | 1.4em |
--cem-data-tree-padding-inline | 0.25em | 0.25em |
--cem-data-tree-column-gap | 0.25em | 0.25em |
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.
--cem-layout-grid-min owns the minimum usable track width for intrinsic,
container-driven grids. Its default 14rem adopts the canonical D1y intrinsic
Grid example. Containers may override this endpoint for their content; components
consume it without local defaults and cap the minimum to available inline space.
This layout threshold is independent of D6's maximum reading-line measure.
Reading content retains its own maximum-measure requirement, and explicit
column counts remain an authored layout choice.
| Token | Value | Description | tier |
|---|---|---|---|
--cem-layout-stack-gap | var(--cem-gap-block) | Default vertical stack gap for general UI stacks | required |
--cem-layout-grid-min | 14rem | Minimum usable track width for intrinsic grids; cap to available container width | 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 |
data-cem-spacing="dense|normal|sparse" may be placed on a container or component
host; the canonical endpoints are inherited by its descendants. An explicit
normal boundary restores the original values of the spacing table's endpoints
even under a dense/sparse ancestor. Omitting the attribute inherits its parent.
Spacing changes gaps and insets; condensed control/tree row heights are selected
separately with data-cem-density="super-compact". Normal spacing alone does not
reset density or its operable minimums. On one boundary, an explicit compact
density selects its complete geometry after spacing; nested boundaries may
choose a different spacing or density for their descendants.
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-data-tree-padding-factor | 0.5 | 1 |
--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.md §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.md.data-cem-spacing="dense|normal|sparse" only if product needs spacing modes.cem-shape.md §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.md); visual geometry values live in D2c.
Controls (cem-controls.md).
| 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-* and menu surface inset/item-gap endpoints | one token per row |
cem-dim-rhythm-reading | --cem-rhythm-reading-* (2 tokens) | one token per row |
cem-dim-rhythm-data | --cem-rhythm-data-* and structured --cem-data-row-* insets/gaps | one token per row |
cem-dim-density-overrides | Precise-input inherited desktop profiles; no new tokens | overrides only |
cem-dim-layout | --cem-layout-* (11 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
Control size belongs to D2c Controls,
while D2 Coupling owns operability and D1 owns layout spacing.
Do not map control size="large" to the D1 spacing token --cem-dim-large.
2.5rem visual
minimum); content and container flex/grid allocation determine the final box.2.5rem / 3rem / 4rem / 6rem / 8rem. Large profiles support vertical rhythm
when actions participate in layouts. They do not fix width or cap growth.data-cem-size; action exposes size as a local override.
Removing it restores inheritance. Other controls can adopt this shared pattern.--cem-coupling-zone-min in both axes
(currently 3rem). Small requested geometry reserves a transparent hit border
inside that box; profiles meeting the minimum need no expansion. This includes
the omitted/default and compact-coupling cases, not just explicit small.The 3rem target is CEM policy, not a claim of automatic ADA conformance. At a
16px root it is 48 CSS pixels; consumers must assess actual computed dimensions.
Size expresses available space and prominence. Content type suggests useful
examples, but does not select size automatically. Keep medium as the API
spelling for an ordinary standalone action; normal is not an additional value.
| Profile | Typical use | Minimum visible height |
|---|---|---|
| Omitted | Content-sized or container-sized action; inherits theme geometry | Inherited |
small | Inline action, table-row control, compact toolbar icon | 2.5rem |
medium | Standalone text action, text with icon, ordinary icon action | 3rem |
large | Prominent confirmation or large playback control | 4rem |
x-large | One choice represented by an image and title | 6rem |
xx-large | Featured choice or CMS hero composition with one action | 8rem |
An icon-only action can use any profile. Glyph size, content layout, intent,
coupling and control height remain independent. The xx-large baseline continues
the 4/6/8rem progression for large controls; it is a starting minimum, not a
fixed hero height or a reuse of the D1 --cem-dim-xx-large spacing token.
Images, wrapping text and responsive container layout determine the final size.
Only requested geometry below the coupling minimum adds the transparent hit
border. Medium and larger default profiles add none, including tiles and heroes.
An overridden profile below that minimum still receives the safety inset.
Compose image/title content through the default slot using noninteractive phrasing content, such as spans, images and strong text. The action permits label wrapping; the author owns stacked or responsive layouts inside the projected content. Size never silently changes row/column layout, font scale or image size. Keep content readable under zoom and narrow containers rather than clipping it to a preset height. A small profile stretched by layout retains its reserved compact inset; this policy follows requested geometry, not DOM measurement.
Semantics follow behavior at every size: a command uses a native button and a
destination uses a native link. The current cem-action owns a native button;
the image and hero playground samples therefore choose content, not navigate.
Adding native-link support is a separate component contract change. A card with
an interactive preview or several actions keeps those controls separate; do not
nest them inside one action. The legacy material demo-list-card follows this
composition: the preview is a sibling of a title/description link with a 6rem
minimum height.
This decision draws on Angular Material buttons, Material 3 Expressive button sizes, icon button sizes, and Angular Material card composition. Material offers size choices for both text and icon buttons; CEM retains its own theme scale and coupling minimum.