Skip to main
maudeMDCC/00

Design-system schema

The names a design system must define so a canvas can switch systems without mapping tokens — 95 core roles, optional slots, the system's own x- names, the component class contract and the checks behind maude design ds-check.

A design system is a folder of CSS, tokens and previews under .design/system/<name>/. The schema is the short list of names every system agrees on. Because a canvas asks for --accent, --bg-1 or .btn and every system answers to those names, the canvas can move from one system to another without anyone mapping tokens by hand.

The schema checks structure only: that a name exists, that it has the right kind of value, that colours exist in both themes. It never judges how a value looks. A system with an unusual palette is still a valid system.

Three tiers

TierWhat it isRequired
Core95 named roles with fixed meanings (--bg-0, --fg-1, --accent, --space-3, …)Yes. A missing role is served by a fallback, but it is reported.
SlotsNumbered families a system may add (--display-1, --palette-3, --chart-2, …)No. A switch pairs slots by kind and index.
OwnThe system's private names: --x-* tokens, .x-* classes, ext.* componentsNo. Handed to AI as context on a switch, never required elsewhere.

A canvas may also declare --c-* for colours that belong to that canvas alone.

Core roles

An asterisk marks a colour role, which must exist in every theme the system declares. A system that declares Light and Dark defines each asterisked name in both. A theme block counts when it is a top-level rule, a rule in a layer, a :root rule (for the default theme) or a light-dark() value (for both).

GroupRoles
Surfaces--bg-0* --bg-1* --bg-2* --bg-3* --bg-4*
Text--fg-0* --fg-1* --fg-2* --fg-3*
Borders--border-subtle* --border-default* --border-strong*
Accent--accent* --accent-hover* --accent-active* --accent-fg* --accent-soft* --accent-on-soft* --accent-text*
Status--status-success* --status-warn* --status-error* --status-info*, each also with -fg, -soft and -text (16 names)
Presence--presence-online* --presence-away* --presence-offline*
Focus--focus-ring* --focus-ring-width --focus-ring-offset
Selection and overlay--selection* --scrim*
Elevation--shadow-sm --shadow-md --shadow-lg
Radius--radius-xs --radius-sm --radius-md --radius-lg --radius-xl --radius-pill
Space--space-0 to --space-8
Fonts--font-display --font-body --font-mono
Type sizes--type-xs --type-sm --type-base --type-md --type-lg --type-xl --type-2xl --type-3xl
Line heights--lh-xs to --lh-3xl, one per size
Weight--weight-regular --weight-medium --weight-semibold --weight-bold
Tracking--tracking-tight --tracking-normal --tracking-wide
Motion--dur-flip --dur-soft --dur-panel --dur-route --ease-out --ease-in-out
Layout--layout-max-w --layout-gutter

The three presence names count only when the system turns the presence family on. The first 68 names come from the original design-system contract; the other 27 (the functional roles such as --accent-on-soft, --status-*-text, --focus-ring, --scrim) were added so that components do not need a private colour for every state.

Value types

Each role has a type, and the checker tests the form of the value, not the value itself.

TypeAccepted forms
colorvar(), hex, a colour function (oklch(), rgb(), color-mix(), …), a named colour, light-dark()
lengthvar(), 0, a dimension, a percentage, calc() and friends
durationvar(), a time (180ms), calc()
easingvar(), an easing keyword, cubic-bezier(), steps(), linear()
numbervar(), a number, calc()
line-heightvar(), a number, a dimension, a percentage, normal
font-familyvar(), a family list
shadowvar(), none, one or more shadow layers

A --focus-ring written as a box-shadow, for example, is a type error.

Slots

Slots are optional families. They matter at switch time: a canvas that uses --palette-4 keeps working on a system with only two palette entries, because the switch writes a pairing for the missing indexes.

KindNamesPairs byIf the target has none
display--display-N, with --display-lh, --display-tracking, --display-weightrankuses --type-3xl
brand--brand, --brand-N, each with an optional -fgindexhanded to AI
palette--palette-N, -fg, --palette-inkindex, wrappinghanded to AI
chart--chart-N, --chart-grid, --chart-axisindexhanded to AI
texture--texture-N-opacity, --texture-N-size, .texture-Nrole (backdrop, surface, overlay), then indexhanded to AI
device.device-N (divider, frame, ornament, mascot, badge)role, then indexhanded to AI
signature-motion--dur-signature-N, --ease-signature-Nrole (celebrate, ambient, playful, emphasis), then indexuses --dur-route
accent-n--accent-2 to --accent-12, with -hover -active -fg -soft -on-soft -textindexuses --accent
extension--shadow-xl, --radius-2xl, --font-serif, --font-rounded, --touch-target, --layout-readingnamenone

"Handed to AI" means the switch's restyle pass sees the canvas's use of that slot and decides; nothing is invented silently.

Own names

KindPatternExample
Token--x-<name>--x-spark
Class.x-<name>.x-island
Componentext.<Name>ext.Island
Canvas-local colour--c-<name>--c-hero-wash

Any other custom property that is neither core, a slot nor --x-* is reported as unprefixed-own. It still works; it is simply not portable, and a switch cannot know what it means.

Aliases and collisions

Many systems predate the schema and use their own spellings, for example --w-semibold for --weight-semibold, --accent-muted for --accent-soft or --ease-spring for --ease-signature-1. The registry carries 69 aliases. An alias is accepted, flagged, and fixed mechanically: the system declares the schema name and keeps the old name as var(--schema-name).

A collision is a name that exists but means something else. The checker finds the ones it can by form:

CollisionHow it is foundWhat it means
Type mismatchthe value's type is not the role's type--focus-ring declared as a shadow
Accent as a hover colour--accent-foreground is also declared--accent means a pale background (shadcn convention); it maps to --accent-soft

and notes the ones only a person can judge (--status-*-fg as text colour, --bg-4 as pressed or elevated, --space-N as multiples of 4px, an overshoot --ease-out). Judgement collisions never change a level; they go to the Bring-up step as notes.

Components

A system ships components.json (schema version 1) next to its CSS. It names the component classes, their variants and sizes, the type-role classes, the icon map and any slots or ext.* components. The class contract is what lets a canvas write <Button variant="secondary"> and have any conformant system draw it.

GroupComponents and classes
Core (14)Button btn, IconButton, Input input, Field field, Switch switch, Checkbox checkbox, Segmented seg, Tabs tabs, Chip chip, Badge badge, Card card, Callout callout, Dialog dialog, Tooltip tooltip
Brand (3)Logo (needs assets/logos/* and preview/logo.*), Icon (needs the icon map), Text (needs the required type roles)
Extended (14)Menu, MenuItem, Toast, Table, ListRow, Avatar, Kbd, Skeleton, EmptyState, Banner, Panel, Sheet, TabBar, CommandPalette
Packsmarketing: FeatureGrid, Testimonial, MarketingCard. mobile: BottomSheet, PullToRefresh

Core components have variants and sizes: Button has primary secondary ghost danger and sm lg; Badge has success warn error info accent; Card has raised interactive flat; Callout has info success warn error. A variant a system lacks is written as null with a fallback naming the variant drawn instead.

State selectors are fixed: :hover, :active, :focus-visible, :disabled, [aria-pressed="true"], [aria-busy="true"], [aria-invalid="true"], [aria-checked="true"] or :checked, :indeterminate, [aria-selected="true"], [open] and [data-side].

Type roles. Eleven classes carry text styles, and ten are required: t-title, t-heading, t-subheading, t-body, t-body-sm, t-caption, t-label, t-eyebrow, t-code, t-num. t-display-N follows the display slot.

Icons. Forty names make up the shared vocabulary: home, search, add, close, check, chevron-up, chevron-down, chevron-left, chevron-right, arrow-up, arrow-down, arrow-left, arrow-right, more, settings, user, users, bell, share, link, trash, edit, copy, download, upload, lock, info, warning, error, success, star, heart, calendar, clock, image, file, folder, play, pause, external. The system’s icon map says which of its own icons answers each name.

Scope and themes. A system's tokens live under the scope class ds (or the system's own root class), and the theme is the data-theme attribute on a wrapper. Canvases use the <DSRoot> wrapper instead of writing either by hand.

Levels and exit codes

maude design ds-check <name> gives a system one of three levels.

LevelExitWhen
Conformant0Every core role, in every theme, with the right type; components and brand present.
Missing roles10Something is absent or non-portable, and a fallback or a mechanical alias fix covers it.
Private-only11A core name is missing outright, exists only under an alias, or has the wrong type, so a canvas cannot rely on it.

Reasons behind the level (an excerpt):

ReasonLevel
missing-core: one of the 68 original names is not declaredPrivate-only
native-only: a missing name exists only under its aliasPrivate-only
type-collision, signature-collisionPrivate-only
missing-functional: one of the 27 added roles is not declaredMissing roles
theme-gap: a colour role is absent in one themeMissing roles
single-theme: only one theme is declaredMissing roles
no-ds-scope: a theme block has no .ds[data-theme]Missing roles
unprefixed-own, alias-not-inverted, unresolved-refMissing roles
components-missing, brand-missingMissing roles
manifest-stale: tokens.json differs from the CSSMissing roles

Other exit codes: 12 when canvas mode finds a blocking finding, 13 when a write flag is refused and nothing is written, 1 for an error and 2 for a usage mistake. A single-theme system is never colour-inverted for you: that would be a visual guess, so it is reported and left alone.

Fallbacks

A level below Conformant does not break a canvas. For each system, Maude injects only the roles and classes that system never declares, in a CSS layer (@layer maude.fallback) placed before the tokens. The layer is the lowest in the cascade, so it can never shadow a value the system sets. Colours in it derive from the system's own tokens (--accent-on-soft falls back to --accent-text), weights are numeric, tracking is 0, and nothing adds a look the system did not have.

What a canvas is checked for

maude design ds-check <name> --canvases runs nine rules on each canvas's .tsx and the stylesheets it imports.

RuleLooks forDefaultWith dsFidelity: strict
S1colour literals in CSS, style and SVG paint attributeswarningblocker
S2var() names that are undefined, belong to another system, or are an aliasblocker (alias is fixed mechanically)blocker
S3type literals on elements without a .t-* classwarningwarning
S4spacing and radius literalswarningwarning
S5imports from system/ (a system's own CSS is removed; kit modules and other systems block)blockerblocker
S6a wrapper that sets both the root class and data-theme by handwarning (fixed mechanically)warning
S7inline SVG paths where the system ships a logo or an icon mapwarningblocker
S8font declarations and font-service linkswarningwarning
S9a missing .meta.json, or a designSystem that is not in config.jsonblockerblocker

Each canvas lands in one of four states, first match wins:

StateMeaning
pinnedmeta.dsPinned: true, or the canvas opted out of the system completely. Left out of every switch.
blockedA blocking finding. The switch lists it unticked, with the reason.
reviewColour literals, --x-* uses, a --c-* block or ext.* imports. It switches, and the restyle pass looks at those.
switchableAnything else.

Pins are computed on every run; only dsPinned is stored.

Commands

snippet
maude design ds-check <name>                    # a system's level; read-only
maude design ds-check --all --json              # every system, machine-readable
maude design ds-check <name> --canvases         # plus every canvas's state and findings
maude design ds-check <name> --emit             # (re)write system/<name>/tokens.json from the CSS
maude design ds-check --canvas "ui/home.tsx" --fix=mechanical --dry-run
maude design ds-check --cheatsheet <name>       # the role cheat-sheet the AI gets
maude design ds-upgrade <name> --analyse        # start Bring-up (never writes the system)

ds-check runs without a prompt in Claude Code: without a write flag it is read-only, and --emit and --fix only write inside the project. In a project shared through a team server, --emit and --fix ask first when the chat has read a file that someone who can see the target canvas is not allowed to see. Autofix is limited to three byte-minimal changes on canvases: an alias becomes its schema name, a system's own CSS import is removed, and a hand-written wrapper becomes ds. System files change only through Bring-up.

  • Multi-DS: several systems in one project and per-canvas attribution.
  • Bootstrap: creating a system with /design:setup-ds.
  • /design:system: opening a system's canvas.

On this page