design-system.json reference
Every field of the design-system.json config, generated from the published JSON Schema so it always matches validation.
On this page
The single source of truth every output format is generated from. Each consumer authors
their own config — there is no shared canonical token set. The field docs below are
rendered from the published JSON Schema, so they always match what vitops validate
enforces.
- Set
"$schema": "https://unpkg.com/@getvitops/generator/schema.json"in the config for editor autocomplete + validation. - Scaffold a starter config with
vitops init; check one withvitops validate. - This can be a standalone file, or live inside a site config. Anywhere the
toolchain takes a config —
--input, the Vite plugin, the Astro integration’scss.input— that file may be adesign-system.jsonor the larger site config that embeds one underdesignSystem.themes.<name>. They are told apart by shape, and the fields below are the same either way. A site config additionally supplies the site-level facts generation reads (its default colour scheme, legal documents, icon sprite), so the path is declared once rather than per option. - The why behind each section: colour system, type & space scales, component patterns.
- What each output format does with these tokens: formats.md.
meta (optional)
Brand identity for agent-facing output. Consumed only by the design format (DESIGN.md); it emits no CSS and no tokens.
name(string) — Brand/system name. Used as thenamefield and<h1>of thedesignformat’sDESIGN.md. Defaults to “Design System”.description(string) — One or two sentences on the brand personality and the feeling the UI should evoke — what an agent needs when no token answers the question. Becomes the DESIGN.mddescriptionfield and opens its Overview section; if omitted, a generic description of the system’s mechanics is used instead.
colors
The colour system (the only required section): palette hues become generated OKLCH scales; roles map semantic roles onto those hues, from which all role tokens and dark mode derive.
palette(map, required) — Palette hues by name. Each becomes an 11-step numeric OKLCH scale (--color-<hue>-50…950). Every ramp shares one fixed lightness ladder, so a step means the same lightness in every hue; only chroma and hue vary. Beyond the outermost authored colour the chroma decays towards a small endpoint value (0.008 light / 0.015 dark) so the near-white and near-black ends keep a whisper of the hue — but that is a ceiling, not a target, so a low-chroma seed stays low and a chroma-0 seed gives a true neutral rather than a tinted one. The ladder is also enforced: if pinned colours leave a ramp non-monotonic (some step darker than the one below it), the build fails rather than shipping a scale whose hover states run backwards.<name>(one of) — A palette hue, authored one of two ways:{ seed, anchors? }generates an 11-step numeric OKLCH scale (50…950) from the seed, or{ tones }supplies a fixed brand kit used verbatim.- one of — Seeded hue: the 11-step scale is GENERATED in OKLCH from
seed(anchors pin specific steps).seed(string, required) — Seed colour (hex or oklch()). An 11-step numeric scale (50…950, tinted near-white → tinted near-black) is generated in OKLCH from it; the seed is preserved at its natural step.anchors(map) — Step → colour overrides (hex or oklch()) pinned VERBATIM at those steps. Every other step takes its lightness from the shared ladder, with chroma and hue interpolated between the anchors — so an anchor is reproduced exactly and is the only step allowed off the ladder. An explicit anchor overrides the seed at that step; two anchors that resolve to the same step are an error.
- one of — Fixed hue: authored brand tones used verbatim; no generation.
tones(one of, required) — Fixed brand kit: authored tones placed verbatim at their nearest steps plus tinted off-white/off-black endpoints; no interpolation. Either an ordered light → dark array or a step → colour map.
- one of — Seeded hue: the 11-step scale is GENERATED in OKLCH from
roles(map, required) — Maps semantic role names onto palette hues. Role names are ARBITRARY — add a key and the generator emits that role’s token set (--color-<target>-<role>[-<variant>]for target bg/text/icon/border), its dark-mode flip and its utility classes.
A value is either a hue name ("danger": "rust") or { "hue": …, "kind": "surface" | "chromatic" }. The kind decides the shape of the token set. chromatic (the default, and what the bare-string form means) is a signal colour: its backgrounds split into tints (bg-<role>-x-muted/-muted) and solids (bg-<role>-solid[-bold|-x-bold]), with deliberately no bare bg-<role> — “how loud?” is a question the author answers. surface is a page/panel colour: it has a bare bg-<role> plus the full emphasis range and text scale.
Dark mode flips automatically; there is no per-appearance scheme grammar. The solid family and its computed text-on-<role> foreground stay mode-stable so a filled button keeps its identity. Six roles are a required core, because the shipped framework CSS references them with no fallback: brand-primary, danger, neutral, surface, ui-primary, warning. Conventional additions are ui-secondary/accent, brand-secondary, info and success.
<name>(one of)- one of — A palette hue name. Shorthand for
{ hue, kind: "chromatic" }. - one of
hue(string, required) — The palette hue this role resolves to.kind(surface | chromatic) —surface— a page/panel colour: gets a barebg-<role>plus the full emphasis range and text scale.chromatic(default) — a signal colour: tints and solids only, no barebg-<role>.
- one of — A palette hue name. Shorthand for
utilities(array of bg | text | icon | border | outline | fill | stroke) — Which colour utility-class families to emit (bg-*,text-*,icon-*,border-*,outline-*,fill-*,stroke-*). Defaults to bg, text, icon, border.iconis a separate non-text tier (a glyph may run more vivid than text);outline/fill/strokehave no tokens of their own and alias the border and icon tiers.
shadows (optional)
Named shadows → --shadow-<name> tokens and .drop-shadow-<name> utilities. Values are shadow parameter lists (offset/blur/colour). Each token feeds two consumers with different grammars — box-shadow (pattern geometry, via the --ds-* group aliases) and filter: drop-shadow(…) (the utilities and the shadow: state shortcut) — so values must stay in the intersection: one layer, no spread radius, no inset. drop-shadow() rejects all three, and rejecting them invalidates the whole filter, so the shadow vanishes rather than degrading.
fonts (optional)
Raw font stacks by name, emitted as --font-<name> tokens (referenced by typography.families). Stacks only — vitops does not load webfonts. A value here is a font-family list and nothing more: it emits no @font-face, no preload, and no metrics-matched fallback. If a family needs loading, declare it in Astro’s fonts: config (astro.config, or the site config’s fonts array) and point the token at the family’s cssVariable — "display": "var(--font-league-spartan), sans-serif". Installing a @fontsource* package and importing its CSS also works but gives up subsetting, preload and size-adjust/ascent-override fallbacks, so it regresses CLS.
typeScale (optional)
Fluid modular TYPE scale → --text-<name> tokens, consumed by typography roles and text-size utilities.
base(string, required) — Anchor size (a CSS length, e.g. “1rem”) — the value atbaseStep.ratio(number, required) — Modular ratio between adjacent steps at large viewports.steps(number) — Token count whennamesis absent (steps are then named 1..steps).names(array of string) — Step names, smallest → largest (e.g. [“xs”,“sm”,“md”,…]); each becomes a token suffix.baseStep(number) — 1-based index of the step whose value isbase.baseline(string) — Named step used as the fluid pivot / GUI scale centre (defaults tobaseStep).fluid(object) — Makes the scale fluid: each step compiles to a clamp() that interpolates fromminRatioatminVwtoratioatmaxVw.minVw(string, required) — Viewport width (CSS length) where fluid scaling bottoms out.maxVw(string, required) — Viewport width (CSS length) where fluid scaling tops out.minRatio(number, required) — Modular ratio at/belowminVw(usually <ratio).
spaceScale (optional)
Fluid modular SPACE scale → --space-<name> tokens, consumed by spacing/gap utilities and vertical rhythm.
base(string, required) — Anchor size (a CSS length, e.g. “1rem”) — the value atbaseStep.ratio(number, required) — Modular ratio between adjacent steps at large viewports.steps(number) — Token count whennamesis absent (steps are then named 1..steps).names(array of string) — Step names, smallest → largest (e.g. [“xs”,“sm”,“md”,…]); each becomes a token suffix.baseStep(number) — 1-based index of the step whose value isbase.baseline(string) — Named step used as the fluid pivot / GUI scale centre (defaults tobaseStep).fluid(object) — Makes the scale fluid: each step compiles to a clamp() that interpolates fromminRatioatminVwtoratioatmaxVw.minVw(string, required) — Viewport width (CSS length) where fluid scaling bottoms out.maxVw(string, required) — Viewport width (CSS length) where fluid scaling tops out.minRatio(number, required) — Modular ratio at/belowminVw(usually <ratio).
patterns (optional)
Component patterns and their token cascade: defaults → groups → per-pattern overrides, plus shape (radii) and z-index primitives.
defaults(map) — Cascade-wide fallback tokens, emitted as--<prop>-default.radii(map) — Shape primitives, emitted as--br-<name>(referenced by pattern bases).groups(map) — Group-level tokens, emitted as--<prop>-<group>; patterns opt in via theirgroupkey.<name>(map) — A CSS declaration block: property → value. Values stay strings (they can be hex, var(), clamp(), keywords, …); the generator, not the schema, interprets them.
z(map) — Z-index tiers →--z-tier-<name>.items(map) — The component patterns to emit, keyed by name.<name>(object) — One component pattern (button, link, badge, card, …): base declarations + interaction states + semantic role variants, resolved through the pattern token cascade.group(string) — Token-cascade group this pattern belongs to (e.g. tag / control / panel); base declarations resolve through--<prop>-<group>before--<prop>-default.overrides(map) — Per-pattern token overrides, emitted as--<prop>-<name>-groupvalues.element(string) — Style at element level via zero-specificity:where(<element>)(instead of, or alongside, a class).class(string) — Class name to emit (defaults to the pattern’s key when noelementis set). Combined withelement, the pattern emits one zero-specificity:where(<element>, .<class>)rule so the class works on any tag and any explicit class overrides it.fill(boolean) — Whether this pattern is colour-filled (states/roles drivebackground-color+on-solidtext) or text-coloured (they drivecolor). Defaults to true whenbasedeclares a background.default_role(string) — Semantic colour role applied to the bare/default variant.base(map) — Base CSS declarations. Geometry properties (padding, border-radius, border, box-shadow, font-size) are wrapped in per-pattern override hooks (--p-<name>,--br-<name>,--b-<name>,--ds-<name>,--fs-<name>) so consumers can restyle one pattern by setting one variable.states(map) — Interaction states (hover / active / focus-visible), each a map of shortcuts:step(intensify the fill or text by n rungs —bg-<role>-solid→-solid-bold,text-<role>→-bold),scale(transform scale),lift(translateY + shadow),shadow(a shadow name → drop-shadow(var(–shadow-)), or true → lift shadow), ring(focus ring), or rawcssdeclarations. Hover rules are wrapped in@media (hover: hover).roles(array of string) — Semantic colour role variants to emit as<pattern>-<role>classes (fills use the role solid / on-solid tokens).
typography (optional)
Typography: family aliases, semantic type roles (→ font-<role> classes), and the bare-element → role mapping.
families(map) — Role-facing family aliases → CSS font values, usually referencing the top-levelfontstokens (e.g. “var(–font-display)”).roles(map) — Semantic type roles (display, title, heading, body, quote, caption, eyebrow, code, lead, footnote, tag, …), each emitted as afont-<role>class.<name>(map) — A bag of CSS-ish keys, each mapped to a declaration plus a--<role>-<sfx>override hook. The recognised set is closed:family,size,weight,style,line-height,tracking(→letter-spacing),text-transform,text-decoration,text-wrap,color. Note the last four are spelled with their full CSS property names —transformanddecorationare NOT accepted. Anything unrecognised is ignored, not passed through, so the generator warns rather than emitting it: a silently-droppedtransform: uppercaseis how title-case navigation reaches production. Note also thatstyle,text-transform,text-decorationandtext-wrapare emitted on every role at their identity value (normal/none/none/wrap) whether declared or not, so applying one role class over another fully resets it — which means omittingtext-wrapis not “inherit”: it emitstext-wrap: wrapand cancels theprettythe role would otherwise inherit from aprettyancestor such as abody-mapped role. Declare it on every role —balancefor heading-like roles,prettyfor copy,wrapfor short single-line labels.<name>(string | number)
headings(map) — Maps bare elements to type roles so unclassed markup picks up role styling —{ "h1": "display", "h2": "heading" }. The key is used verbatim as a selector, so it is not limited to h1…h6: map"body"to your prose role to bind base page typography to the role rather than hand-writing it. That binding is what makes the role editable — a stylesheet that re-statesfont-family/line-heightas literals onbodyshadows--<role>-ff/--<role>-lh, and the live theme editor then appears to do nothing.
animations (optional)
Animation effect + journey classes (pure value layers). The animation engine itself — keyframes, drivers, floats, utilities — is static framework CSS, not configured here.
effects(map) — Effect classes to emit (.fade-in,.reveal-left, …), keyed by class name.<name>(object) — A named animation effect class — a pure value layer (--_anim+--<prop>-from/-to) over the static keyframe engine.kf(string, required) — Keyframe family driving the effect: composite (transform/opacity), paint, or layout.css(map) — Extra literal declarations merged into the effect class as-is.<name>(string | number)
vars(map) — Effect endpoint variables (--<key>: <value>, e.g. opacity-from, translate-y-to) that override the keyframe defaults.<name>(string | number)
journeys(object) — Multi-part journey classes composed frombasebuilding blocks.base(map) — Named journey building blocks: part name → var map.compose(array of array) — Combinations of base parts, each emitted as a.<parts>-journeyclass.