Skip to main content
Substrate has no spacing scale — no numbered token ladder, and no table of multiples to memorize. A brand declares a single unit, and every use site multiplies it by its own coefficient and by the runtime density and scale factors, which means changing density is a live preference change rather than a rebuild.

The space block

One field. The bundled demo brands all use the same value:
unit is a bare number in pixels, nested under space. It emits as --space-unit: 4px on the brand selector.

How spacing is applied

Instead of consuming a named token, a component multiplies the unit by its own coefficient inside a calc(). From generated/global/css/components/badge/badge.gen.css:
Two things follow from this shape that a ladder cannot give you. Coefficients are fractional — the badge’s 0.5 and 1.5 are half and one-and-a-half units, positions no integer scale contains. And every value re-resolves whenever --density or --scale changes, with no regeneration, because the multiplication happens in the browser rather than in the pipeline. Corner radius rides the same factor. From generated/global/css/tokens.gen.css:
So a denser layout gets tighter corners automatically, keeping the shape language proportional to the spacing around it.

Density is continuous

densityFactor is an axis on the runtime preference vector, running from 0.8 to 1.3. It is not a mode, not an enum, and not a build input. A brand bounds how far users may move it with the flexibility.density block:
Most of the bundled demo brands use exactly that range; one opens the lower bound slightly to 0.75.

Density presets are named positions, and brands name them

presets.density is an open map of named levels, each declaring where it sits on the continuous factor. Most brands use the conventional three:
But the names are the brand’s to choose. Imagine the fictional Acme brand serving two contexts from one config — a marketing surface and a product surface — and naming its density levels after them rather than after their tightness:
discovery is the roomy marketing setting and workspace the tight product one. Same continuum, same factors as the conventional trio — different vocabulary, chosen because it matches how a team talks about its surfaces.
Because these are presets over a continuum rather than an enumeration of allowed states, a user is never limited to the named levels. Any value inside the brand’s flexibility.density bounds is reachable, and the presets are convenience points on the way.
A preset level can also carry property deltas that apply while it is active — a font-weight nudge, a padding adjustment — using the same additive ("+0.05") or replacement (bare value) convention as component config. That lets a density level do more than move one number, when a brand needs it to.

Changing density

Nothing rebuilds. The runtime writes --density, every calc() that references it re-resolves, and layout reflows — components, radii, and any density-linked type coefficients together. A brand switching a surface from workspace to discovery is setting a preference, not running a pipeline.
If you are porting from a system with a numbered spacing ladder, resist recreating one on top of --space-unit. Named steps would freeze the coefficients at generation time and cut the density factor out of the loop, which is the one thing this model exists to prevent.

Typography

The same continuous treatment applied to type, with fractional role exponents.

Brand Config Overview

The full schema, the required sections, and the presets block in context.