Skip to content

CSS variables

Every var(--…) an emitted stylesheet references is defined here: variables, text styles, effect styles, fill styles, and mode override blocks, derived from your fetched library data. Together they render with no hand-maintained token file anywhere in the loop.

Unlike everything else on these pages, this is library-level — written once per run, not once per component, into assets/cssvars/ at the workspace root.

assets/
└── cssvars/
├── cssvars.css all variable definitions + mode override blocks
└── modes.json collection → attribute, mode names, default

It is platform-neutral and identical whichever target produces it, so specs react and specs webcomponents write the same file. Running both is not a conflict; the second write is a no-op when the content matches. A scoped run (--components) writes nothing here — a subset of components cannot have invalidated the library’s tokens.

What it emits

KindEmitted as
Variables--{collection-name}-{variable-path}, matching the stylesheet’s derivation exactly. Aliases resolve to var() references; aliases pointing outside the fetched payload are skipped and counted
Collection modesAttribute-scoped override blocks per local multi-mode collection
Text stylesA font shorthand, plus a -letter-spacing companion when present
Effect stylesOne variable per role the style uses — shadows, layer blur, background blur
Fill stylesA color or CSS gradient function, from the style’s topmost visible paint
:root {
--color-surface-primary: var(--palette-neutral-100);
--body-small: 400 12px/16px "Inter", sans-serif;
--elevation-raised-shadows: 0 4px 4px 0 rgba(0, 0, 0, 0.25);
}
:root[data-theme="dark"] {
--color-surface-primary: #000000;
}

Only the effect roles a style actually uses are emitted. The stylesheet applies all three with none fallbacks, so an absent role is a no-op rather than a break.

Modes

modes.json describes each switchable collection — its attribute, its mode names, its default — so a theme toolbar can be built without parsing the CSS.

{
"Color": { "attr": "data-color", "modes": ["Light", "Dark"], "default": "Light" },
"Space": { "attr": "data-space", "modes": ["Cozy", "Compact"], "default": "Cozy" }
}

Switching a mode is stamping the attribute on the root element. Nothing re-renders and no component knows it happened.

What it reads

The workspace data directory (dataDirectory in config), populated by specs fetch:

FileFor
{alias}.variables.jsonvariable and collection definitions
{alias}.file.jsonstyle values, recovered from nodes that use each style — the styles endpoint carries no definitions
{alias}.styles.jsonnames for published styles

It degrades rather than failing. Without variables data — on a plan where the variables endpoint is unavailable — it still emits everything recoverable from the file document. A style no node in the file uses cannot be recovered, and is reported in the run summary.

Name sanitization

Names pass through the same derivation the stylesheets use, so a reference and its definition always agree. Characters invalid in a CSS custom property name are dropped, and a warning summary with per-name counts prints at the end of the run.

See Also

  • Styles — the stylesheets these variables resolve
  • fetch — populates the data these are derived from
  • tokens setting — how token references are serialized in spec output