What is DSDS?

DSDS (Design System Doc Spec) is a YAML format for documenting design systems. It puts every piece of docs — components, tokens, themes, and anything else — in a machine-readable shape: a graph of entries, each carrying typed sections.

Write your documents in YAML — every example on this site, the default npm run check sweep, and the tooling all assume it. The schema itself is written in JSON Schema (that's the language the rules are written in, a separate thing from the format your documents need to be in). Since YAML is a superset of JSON, a JSON document would technically still parse — but nothing in this repo discovers or exercises that path, so don't rely on it.

DSDS documents the how and why of your design system — not the token values themselves. It complements the W3C Design Tokens Format which handles the what.

What you get
  • Structured — every section has a defined shape, no guessing
  • Machine-readable — tools can parse, generate, validate, and transform it
  • Portable — not locked to any docs tool or platform
  • Extensible — add vendor metadata without breaking compatibility
  • Validatable — the schema catches errors before they reach consumers
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@ @@ @@@@@@@@@@@ @@@@@@@@@@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@@@@@@@@@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@@@@@@@@@@ @@@@@@@@@@ @@ @@ @@ @@ @@@@@@@@@@@ @@@@@@@@@@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@@@@@@@@@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@@@@@@@@@@ @@@@@@@@@@ @@ @@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Document structure

A DSDS document is a base: schemaVersion, a name, and a list of entries.

The bare minimum

This much is valid YAML, but not a valid document yet — entries is required:

schemaVersion: "0.20.0" name: Acme Design System
Adding a system entry

System-wide facts (version, organization, url, license, platforms) live on this list's own kind: system entry, not on the base document directly:

schemaVersion: "0.20.0" name: Pizza Party Design System entries: - id: pizza-party-design-system kind: system name: Pizza Party Design System description: The design system behind Pizza Party Super-time. metadata: organization: Pizza Party Super-time LLC version: 0.1.0 status: {status: alpha} platforms: [web-component]
Adding more entries

Any entry — a component, a token, a theme, or the generic entry kind — can sit alongside the system entry in the same entries array. Here, a system entry plus a component entry:

schemaVersion: "0.20.0" name: Pizza Party Design System entries: - id: pizza-party-design-system kind: system name: Pizza Party Design System description: The design system behind Pizza Party Super-time. metadata: organization: Pizza Party Super-time LLC version: 0.1.0 status: {status: alpha} platforms: [web-component] - id: button kind: component name: Button description: Triggers an action.
Splitting across files with refs

For a larger system, keep each entry in its own file and point at it with refs (rel: file) instead of inlining everything — pointing at a sibling document that owns another entry:

schemaVersion: "0.20.0" name: Pizza Party Design System entries: - id: pizza-party-design-system kind: system name: Pizza Party Design System description: The design system behind Pizza Party Super-time. metadata: organization: Pizza Party Super-time LLC version: 0.1.0 status: {status: alpha} platforms: [web-component] refs: - href: ./components/button.dsds.yaml rel: file role: Button component, documented in its own sibling file

Use a multi-file split for large systems where a different team owns each component. Use one file with everything inlined for smaller systems. scripts/compose.js can concatenate many hand-authored fragment files into one document before validation — see the repo's own examples/base/.

Entry kinds

Every entry has a kind field. There are 5 well-known values, plus an open option for anything else.

KindDescription
systemThe design system as a whole — version, organization, url, license, platforms, plus system-wide documentation.
componentA reusable UI element — buttons, inputs, modals. Carries its own sourceFiles, imports, traits (variants and states), and combos, on top of the fields every entry shares.
tokenA single design token. Carries tokenType and a source pointer to the real DTCG value — never the value itself.
themeA named set of token overrides — dark mode, high-contrast, a brand variant. Points at its own DTCG source file.
entryThe generic, open kind for anything else — a foundation, a pattern, a guide. Has no fields beyond what every entry shares.
(custom)A custom kind like acme.icon-library, for a document that wants its own recognizable name instead of the generic entry.
Fields every entry shares

Every entry kind shares one open base: id, kind, name, description (required), plus purpose, metadata, related, extends, refs, sections, $extensions (optional).

related, extends, and refs are three separate pointer lists, not three names for the same thing — each is scoped to a different kind of connection: related for entries that are similar in usage (rel: alternative-to, rel: pairs-with), extends for inheritance (rel: extends), and refs for anything else, including outside resources (rel: source, rel: storybook). This entry-level refs is the same field name as the base document's refs used above to split into files (rel: file) — same mechanism, different level, different job.

See How the schema is organized for how the schema itself is put together.

Status

Status lives in metadata.status, always as an object — there's no bare-string shorthand. A component-wide status:

metadata: status: {status: stable}

Scope a status to one platform when a component ships on more than one:

metadata: status: platform: react status: deprecated deprecationNotice: Use icon-button instead — this variant never got contrast-tested.
The section system

Structured docs live in the sections array on each entry. Each section has a kind field naming its type — guidelines, definitions, steps, or the generic section. Any entry kind can use any section kind; nothing restricts which section kinds go with which entry kind.

Every section also carries a for field (human, agent, or all) naming its audience — see Humans and agents on the Overview page.

Guidelines: rules paired with why they exist

Each guideline item pairs a statement with a level (an RFC 2119 requirement level) and, optionally, alternatives, evidence, or a checkedBy/checks verification pair. A component with a guidelines section:

id: button kind: component name: Button description: Triggers an action. purpose: Gives users a single, consistent way to trigger an action across the product. metadata: status: {status: stable} tags: [actions, form] sections: - kind: guidelines for: all items: - statement: Limit each surface to one primary button. level: should

The level field's values are lowercase kebab-case, like every DSDS vocabulary: must, should, should-not, must-not, may. Tools display them as badges: MUST, SHOULD, SHOULD NOT, MUST NOT. Agents treat must/must-not items as hard limits.

A guidelines section also carries framing: when-to-use for a fit judgment (is this entry the right choice at all), or how-to-use (the default) for an implementation rule once it's chosen.

Definitions: a glossary, anatomy, or prop list

A definitions section pairs a term with its definition — use it for anatomy parts, naming conventions, or a component's own prop/event list when there's no real source file to extract from. context (a field every section kind can carry, not just definitions — see sections/section) names which of those jobs it's doing (anatomy, terms, keyboard, events, or a namespaced custom value) — optional, but it's what lets a tool find "the anatomy table" without matching on the human-facing title. Here's an anatomy definitions section:

sections: - kind: definitions for: all title: Anatomy context: anatomy items: - term: Container definition: The interactive root element. Receives background, border, radius, and padding. - term: Label definition: The visible text of the button.
Steps: a procedure or checklist

A steps section is an ordered procedure or an unordered checklist (ordered: false). Each item can point back at the guideline it verifies via checks (rel: depends-on). A self-check checklist:

sections: - kind: steps for: agent ordered: false title: Self-check before shipping items: - title: Icon-only buttons have an aria-label checks: - to: button#aria-label-required rel: depends-on
Freeform: narrative prose

Every section kind — including the generic section — can also carry freeform: headed, nestable prose alongside its own structured items:

sections: - kind: section for: all title: Overview freeform: - title: About body: Button is the primary interactive primitive in this design system.
A component's own top-level fields

Unlike sections, a few facts about a component live directly on the entry, not inside a section — they're facts about the component as a build artifact, not documentation content:

  • sourceFiles — one entry per platform, pointing at the real source file a tool can extract the component's API from. Replaces hand-typed prop tables.
  • imports — one entry per platform, with the install package and the exact import statement.
  • traits — every way the component can vary: a kind: enum dimension (like size: sm | md | lg) or a kind: boolean toggle (like hover or loading). A trait's own optional setBy (consumer or component) says whether it's a value the consumer sets, or a condition the component sets on its own — kind alone doesn't tell you that, since a boolean trait can be either (disabled is usually a prop the consumer passes in; hover never is).
  • combos — pairing rules between traits, tokens, or entries (e.g. "loading and disabled must not both be set").

Here's sourceFiles, traits, and combos together:

sourceFiles: - platform: react file: ./src/Button.tsx traits: - id: tone kind: enum name: Tone setBy: consumer values: - id: default description: Neutral. General-purpose actions. - id: critical description: Destructive or irreversible actions only. - id: loading kind: boolean name: Loading description: An async operation triggered by the button is in progress. setBy: consumer - id: hover kind: boolean name: Hover description: Background darkens slightly when the pointer is over the button. setBy: component combos: - subject: loading level: should-not items: [disabled] note: Loading already makes the button non-interactive.
Going beyond the basics$extensions

$extensions is a place for vendor or tool-specific data, at the document, entry, or section level. Keys MUST use a namespace (e.g. com.figma) so a tool integration never collides with a future field the spec adds. Here, linking a component to its Figma source:

id: button kind: component name: Button description: Triggers an action. $extensions: com.figma: context: Links this entry to its source Figma component for design-file lookups. nodeId: "12:4045"
Custom kinds

When the generic entry kind isn't specific enough, use a custom kind instead — it's checked against the same open entry.schema.yaml base the generic kind is:

id: material-icons kind: acme.icon-library name: Material Icons description: The icon set Acme's products ship with.

See Extending the schema for a third option too — profiles, for making an existing kind's optional fields required on your own project — and for more on when to reach for each of the three.

Minimal examples

These are close to the smallest valid entry for each kind. Copy one, fill in your content, and add sections as your docs grow.

Component

A minimal standalone component entry:

id: button kind: component name: Button description: Triggers an action.
Token

A token needs id, kind, name, description, and (usually) tokenType. Use source to point back at the DTCG file that holds the real value:

id: color.action.primary kind: token name: Action Primary description: The fill color for high-emphasis interactive surfaces. tokenType: color

A described token adds metadata.group (the recommended way to group related tokens — there's no separate token-group kind) and a guideline:

id: color.action.primary kind: token name: Action Primary description: The fill color for high-emphasis interactive surfaces. tokenType: color source: ./tokens.dtcg.json metadata: status: {status: stable} group: color.action sections: - kind: guidelines for: all items: - statement: Pair with `color.surface.default` or `color.surface.raised` only — contrast is unverified on any other background. level: must
A pattern, using the generic entry kind

A pattern, documented as a generic entry:

id: empty-state kind: entry name: Empty State description: A pattern shown in place of a list or grid that has no content yet. purpose: Tells the user why an area is empty and what to do next, instead of leaving a blank surface. sections: - kind: guidelines for: all framing: when-to-use items: - statement: Use an empty state the first time a user visits a list or grid that has no content yet. level: should
Shared content

Content that isn't itself a design-system artifact — an accessibility rule that applies to more than one entry, stated once and pointed at from everywhere it applies — lives in the base document's shared array, addressed via entryId#itemId and rel: same-as — a shared rule, referenced instead of restated:

schemaVersion: "0.20.0" name: Pizza Party Design System entries: - id: button kind: component name: Button description: Triggers an action. sections: - kind: guidelines for: all items: # No `statement` here — this item IS the "touch-target" rule # declared once on shared-a11y below, not a second copy of it. - level: must refs: - to: shared-a11y#touch-target rel: same-as shared: - id: shared-a11y name: Shared Accessibility Rules description: Cross-cutting accessibility rules, stated once and referenced from every entry they apply to. sections: - kind: guidelines for: all items: - id: touch-target statement: Minimum touch target 44x44px. level: must
Validate your documentUsing the bundled schema

Add $schema to get editor autocompletion and inline validation:

$schema: https://designsystemdocspec.org/v0.20.0/dsds.bundled.yaml id: my-component kind: component name: My Component description: What this component is and does.
Using the CLI# Clone the repo git clone https://github.com/somerandomdude/design-system-documentation-schema.git cd design-system-documentation-schema npm install # Validate the built-in examples and test corpus npm run check # Validate your own file node scripts/validate.js my-system.dsds.yaml Next steps

You've seen the basics. Here's where to go deeper.

ResourceDescription
Full SpecComplete schema reference for every field and constraint
Schema filesThe raw .schema.yaml files — use for editor autocompletion
Example filesComplete, valid example documents for every entry and section kind
GitHub DiscussionsAsk questions, share ideas, propose changes
  1. Copy the minimal component example above
  2. Replace it with your own design system's first component
  3. Add sections as your docs grow
  4. Validate with npm run check to catch schema errors early