Design System Contracts

Spec reference

The contract document

Identity, lifecycle, anchors, and the top level of the document — the shape everything else hangs from.

Top-level fieldsGeneratedCurated#

A contract is one JSON document per component — contracts/<component>.contract.json. It captures everything design and engineering must agree on; both libraries are renderers of it. Types below are rendered from the schema; follow the links for the fields that have their own pages.

$schemastring

Editor affordance: points at the emitted JSON Schema (contracts/contract.schema.json, npm run schema) so contracts validate inline as you type.

idrequiredstring

Stable canonical identity — never renamed. The namespace before the dot is the owning system’s (ds.button here, acme.chip in a brownfield extraction). Display names may change on either side; the id is what survives.

namerequiredstring

Display / export name (Button). Drives the code export and the canvas component-set name. Must be PascalCase — it becomes file names.

versionrequiredstring

Semver, the unit of change management. Added optional prop = minor; removed or renamed prop or value = major. See versioning.

statusrequired"draft" | "stable" | "deprecated" (default: "draft")

Governance lifecycle: draftstabledeprecated.

descriptionrequiredstring

Usage intent — one sentence, flowing into Storybook autodocs and the canvas component description. The same sentence on both surfaces, from one source.

semanticsrequired{ element: "button" | "span" | "div" | "a" | "input" | "article" | "section" | "header" | "footer" | "label" | "nav" | "hr" | "ul" | "li" | "p" | "textarea" | "select" | "fieldset" | "blockquote" | "code" | "kbd" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; role?: string; roleException?: string; roleByProp?: { prop: string; map: Record<string, string> }; elementByProp?: { prop: string; map: Record<string, string> } }

The HTML element, ARIA role, and prop-driven variants of both — see Semantics & accessibility.

propsrequiredProp[]

The canonical API — see Props & bindings.

eventsEvent[]

The declared interaction surface — see Events & toggles.

statesrequiredArray<"hover" | "active" | "focus-visible" | "disabled"> (default: [])

Interaction states the component must support — see States.

figmaRepresentation"component" | "native"

component (default) generates a canvas component set. native declares the concept maps to a native canvas capability (layout primitives are auto-layout): no canvas component is generated, parity does not expect one, and the code surface is still fully generated and checked.

modesstring[]

Receipt-grade metadata naming the token modes a drawn theme/mode variant axis carried (e.g. ["light","dark"]). The axis is never a prop — theming lives in the token collection’s modes — so this field changes no emitter output; it names the fact for reviewers and round-trip tooling.

figmaStatePreviewsboolean

Opt-in canvas-only state previews — see States.

anatomyrequiredRecord<string, Part>

The named part tree where all styling decisions live — see Anatomy & parts.

a11y{ focusVisible?: boolean; minHitArea?: number; contrast?: "AA" | "AAA" }

Executable accessibility requirements — see below.

anchorsrequired{ figma: { fileKey: string; componentSetKey: string; nodeId?: string }; code: { importPath: string; export: string } }

Per-side identity anchors — see below.

Anchors — rename-safe identityGeneratedCurated#

The DTCG $extensions dual-ID pattern applied to components. After the first generation on each side, that side’s stable identifiers are written back here (npm run anchors:writeback). From then on, renames on either side never fork identity — parity matches by anchor, not by name.

figmarequired{ fileKey: string; componentSetKey: string; nodeId?: string }

The design-tool side: file key, component-set key, node id. null until first sync — the differ reports such contracts as pending, which is workflow state, not drift.

coderequired{ importPath: string; export: string }

The code side: import path and export name of the generated component.

contracts/button.contract.json (excerpt · v1.5.0) — shipping contract, loaded at build time
{
  "id": "ds.button",
  "name": "Button",
  "version": "1.5.0",
  "status": "draft",
  "anchors": {
    "figma": {
      "fileKey": "8nim1d0IPnehMxA7B7SYxC",
      "componentSetKey": "1b5d2a573f3f39404af396bdbe944a30ca0eaec3",
      "nodeId": "5:21"
    },
    "code": {
      "importPath": "src/components/Button",
      "export": "Button"
    }
  }
}

a11y — declared requirementsGeneratedCurated#

focusVisibleboolean

The component must have a visible focus treatment (drives the focus-visible state rules).

minHitAreanumber

Minimum hit area in px.

contrast"AA" | "AAA"

Target WCAG contrast tier.

Honesty note: the a11y block is declarative-only today — minHitArea and contrast are recorded but no generator or differ enforces them yet. The roadmap names the choice: enforce or remove. (This caveat is stated in docs/07 — Validation, and this page repeats it rather than hiding it.)