Skip to content

This page renders AGENTS.md from the repository root, then each guide of agents/, so people and agents read the same rules.

Quantum Design System — agent guide

This repository is the monorepo of the Quantum Design System (Thales). It holds technology-agnostic component specifications, DTCG design tokens, Markdown documentation and a vanilla CSS reference implementation, plus an Astro docs site that renders all of it so people can review a component before it is built in any technology, a Figma plugin that draws every component in Figma, bound to variables recreated from the tokens, a Penpot plugin that draws them in Penpot, bound to Penpot tokens, and themes that bring the tokens to Bootstrap 6 and shadcn/ui through those frameworks' own theming APIs. The packages are published on npm under @quantum-ds (@quantum-ds/tokens, @quantum-ds/components, @quantum-ds/figma, @quantum-ds/penpot, @quantum-ds/bootstrap-theme, @quantum-ds/shadcn-theme).

You (an agent or a human) turn natural-language requests such as "a button must have a blue border using the primary color, in three variants" into coordinated updates across every artifact.

How these instructions are organised

  • This file is loaded in every session (CLAUDE.md imports it). It holds only what every session needs: the system in brief, where to read more, the skills, the commands and the rules that always hold.
  • The guides in agents/ hold the detail. Before acting, read the guides the table "Read before acting" names for the task, in full. They are the source of truth for how to work; this file only summarises them.
  • Directory AGENTS.md files (packages/components/specs/, packages/tokens/, packages/components/, packages/figma/, packages/penpot/, apps/docs/content/, apps/docs/screens/, apps/docs/, scripts/) add local rules. Read the one of every directory you are about to edit.
  • Skills in .claude/skills/ run recurring workflows step by step (see "Skills").
  • Changing these instructions: a rule goes in this file only when every session needs it and it fits in a line; everything else goes to its guide or directory file. All of them are rendered on the site's "How it works" page.

The system in brief

Four artifacts describe one component and must always agree (pnpm check proves it): the spec packages/components/specs/components/<id>.spec.json (what it is, with its decision log), the component tokens packages/tokens/src/components/<id>.tokens.json (every design decision), the docs apps/docs/content/components/<id>.md (usage guidance, prose only) and the web implementation packages/components/src/components/<id>/ (CSS reading only component tokens, a pure HTML template). A foundation (packages/components/specs/foundations/, apps/docs/content/foundations/) is a rule every component follows, such as color or the focus ring. A screen (apps/docs/screens/) is a real-world page rebuilt only with the components and tokens. The site in apps/docs/ is a viewer built with the design system: it never encodes design knowledge of its own. A Figma blueprint (packages/figma/src/blueprints/<id>.mjs) redraws each component in Figma: its layer tree, read from the web implementation, naming the token slot of each field, bound to Figma variables recreated from the tokens (packages/figma/AGENTS.md).

raw (hidden primitives)        <-  semantic (what components read)        <-  component (per part)
packages/tokens/src/raw/**         packages/tokens/src/semantic/**             packages/tokens/src/components/<id>.tokens.json
                                   packages/tokens/src/additions/semantic/**

The same decisions hold on every platform (web, ios, android, desktop) and in every mode (light, dark): only raw and semantic values are overridden, in packages/tokens/src/platforms/<id>/ and packages/tokens/src/modes/<id>/. The outputs under packages/tokens/dist/ (built, not committed) come from pnpm tokens:build: the CSS the repository reads, the platform trees and the syntax theme (scripts/build-tokens.mjs), and the published CSS, Sass, JavaScript, Android, Figma, Penpot and Tokens Studio formats (Style Dictionary, packages/tokens/style-dictionary.config.mjs). Details: agents/model.md.

Read before acting

The session is about… Read
Understanding the system, finding where something lives agents/model.md
Any change to a component, a foundation or a token: a request, a stated decision, a new component agents/workflow.md, agents/conventions.md, agents/impact.md, then the directory files of what you edit (packages/components/specs/, packages/tokens/, packages/components/, apps/docs/content/)
Anything touching accessibility: a role, keyboard, naming, contrast, a cited statement agents/accessibility.md, packages/components/specs/a11y/
The Figma library and its local export, a Figma export, plugging tokens, where a token comes from agents/token-sources.md, packages/tokens/AGENTS.md
Drawing the components in Figma, a Figma blueprint, the Figma plugin agents/token-sources.md "Drawing a component in Figma", packages/figma/AGENTS.md
New releases of Figma, Penpot or Tokens Studio, whether the design tool exports are up to date, a workaround of a tool's limit agents/design-tools.md, agents/design-tools.json
Drawing the components in Penpot, the Penpot plugin agents/token-sources.md "Drawing a component in Penpot", packages/penpot/AGENTS.md, packages/figma/AGENTS.md (the blueprints)
Specs format export or import agents/specs-interop.md
A screen apps/docs/screens/AGENTS.md, agents/impact.md
The docs site apps/docs/AGENTS.md
Scripts, the validator, the token build scripts/AGENTS.md
Committing, a branch, a pull request, an issue (the end of every change) agents/git.md

Skills

Skill Use it when
stepwise-pr Work needs several team decisions in a row: a draft pull request whose description is the plan, one step per session ("continue step N").
figma-alignment A component is compared with or improved from its local Figma export (agents/token-sources.md). Builds on stepwise-pr.
review-sheet The team must compare renderings to decide: a focused before/after page and one precise question.
worktree-preview Someone needs a local preview of a branch, a pull request or a worktree under .claude/worktrees/.
backlog Out-of-scope work becomes an issue, a merged study becomes issues, or the open issues need syncing or labelling.
framework-theme A theme of Quantum for a UI framework (Bootstrap, shadcn/ui, another one) is created as a @quantum-ds/<framework>-theme package from a mapping checked against the tokens and the specs, or regenerated after a token or spec change.
design-tools-watch The releases of Figma, Penpot, Tokens Studio or DTCG need reviewing against the exports, or a check in the real tool comes back (agents/design-tools.md).

Claude Code lists them by itself and invokes them by name; another agent reads .claude/skills/<name>/SKILL.md.

Commands

Command What it does
pnpm install Install the root tooling and every workspace package (packages/*, apps/*); installs the commit-msg hook.
pnpm tokens:build Check the tokens and build packages/tokens/dist/ (not committed) from packages/tokens/src/: dist/css/** (raw.css, semantic.css, components/<id>.css and the index.css entry point), dist/platforms/<id>/<mode>.tokens.json and dist/shiki-theme.json (scripts/build-tokens.mjs), then the distribution formats (packages/tokens/style-dictionary.config.mjs). Run after any token change.
pnpm components:build Copy the specs (JSON and the foundations' code samples) to packages/components/dist/, what @quantum-ds/components publishes; not committed, also run by pnpm build and before packing.
pnpm tokens:figma-library Report every token recreated from the Figma library that has since been added, removed or changed; needs the local export (agents/token-sources.md).
pnpm figma:export [id…] Build the Figma plugin into packages/figma/dist/ (not committed): it draws the components (all, or the given ones and those their instances use) as component sets bound to Figma variables recreated from the tokens (packages/figma/AGENTS.md).
pnpm penpot:export [id…] Build the Penpot plugin into packages/penpot/dist/ (not committed): it writes the Penpot tokens and draws the components (all, or the given ones and those their copies use) as components with variants bound to them, from the Figma blueprints (packages/penpot/AGENTS.md).
pnpm penpot:serve Serve packages/penpot/dist/ at http://localhost:4400, where Penpot's plugin manager installs manifest.json (Penpot loads plugins over HTTP).
pnpm bootstrap-theme:build Build the tokens, then the Bootstrap 6 theme into packages/bootstrap-theme/dist/ (not committed): the theme as Bootstrap root custom properties and as Sass configuration, and demo.html, a self-contained page showing every Bootstrap component with it (packages/bootstrap-theme/AGENTS.md).
pnpm shadcn-theme:build Build the tokens, then the shadcn/ui theme into packages/shadcn-theme/dist/ (not committed): the theme as shadcn CSS variables and as a registry theme item, and demo.html, a self-contained page showing every shadcn component with it (packages/shadcn-theme/AGENTS.md).
pnpm validate Schema-validate specs and cross-check spec ↔ tokens ↔ docs ↔ web; check mirrored components render the same; check the docs site and the screens are built with the design system.
pnpm specs:check Export every component to the Specs format and require a valid, lossless round trip (agents/specs-interop.md).
pnpm specs:export [id…] Write <id>.specs.json + <id>.quantum.json sidecar to build/specs/.
pnpm specs:import <file> [--sidecar <file>] Turn a Specs document into a draft spec + component tokens in build/specs-import/. Never writes into packages/components/specs/ or packages/tokens/src/.
pnpm test Unit tests of the scripts' libraries (scripts/test/), of the Figma and Penpot plugins (packages/figma/test/, packages/penpot/test/) and of the Bootstrap and shadcn themes (packages/bootstrap-theme/test/, packages/shadcn-theme/test/).
pnpm check tokens:build + validate + specs:check + test. Must pass before any change is considered done.
pnpm impact [--base <ref>] [--files …] Every artifact linked to what changed (screens included), and whether it changed too. Fails when a component changed without a version bump (agents/impact.md).
pnpm commits:check [--base <ref>] [--message "<text>"] Check commit messages against Conventional Commits (agents/git.md).
pnpm a11y:wcag Regenerate packages/components/specs/a11y/wcag.json from the W3C's machine-readable WCAG 2.2 (network).
pnpm new:component <id> --name "Name" --role <role> Scaffold the four artifacts for a new component and a starter Figma blueprint (to rewrite from the real CSS and template), build the tokens, register it in packages/components/src/index.css.
pnpm dev Build tokens, then start the Astro docs site (apps/docs/) at http://localhost:4321.
pnpm build Build tokens, the components package (packages/components/dist/), the Figma and Penpot plugins (packages/figma/dist/, packages/penpot/dist/), the Bootstrap and shadcn themes (packages/bootstrap-theme/dist/, packages/shadcn-theme/dist/) and the static docs site into apps/docs/dist/.
pnpm preview Serve the built docs site locally.

Rules that always hold

  • A change is the whole set of artifacts linked to it, screens included: spec → component tokens → pnpm tokens:build → CSS/template → Figma blueprint → docs → screens, then pnpm check and pnpm impact (agents/workflow.md, agents/impact.md).
  • Every stated decision goes through the decision loop in full and is recorded in the spec's decisions[], one decision per commit (agents/workflow.md "Decisions").
  • Accessibility targets WCAG 2.2 AA; every accessibility statement cites a trusted source you opened (agents/accessibility.md).
  • No raw or semantic token beyond the Figma library and the listed additions without asking first (agents/token-sources.md).
  • One development, one branch, one pull request against main, Conventional Commits; a human reviews and merges. Out-of-scope work becomes an issue in ThalesGroup/quantum (agents/git.md).

Do not

  • Edit or commit anything under packages/tokens/dist/.
  • Put literal colors or lengths in packages/components/src/components/**/*.css.
  • Reference raw tokens from component tokens or semantic tokens from CSS.
  • Encode component knowledge in apps/docs/ (variants lists, states, colors). The site derives everything from the four artifacts.
  • Style the site with literal colors or font values, raw tokens or component tokens, or give it a look-alike of a control the design system has a component for (apps/docs/AGENTS.md).
  • Duplicate spec data in docs (option lists as data). Docs explain why and when; the spec defines what.
  • Add a dependency to scripts/build-tokens.mjs and its libraries. The token checks and the CSS the repository reads are intentionally zero-dependency so they can be lifted into other tooling; the distribution formats belong to packages/tokens/style-dictionary.config.mjs (Style Dictionary).
  • Commit with pnpm check failing.
  • Commit or push to main directly, merge your own pull request, or enable auto-merge (agents/git.md).
  • State an accessibility behaviour, requirement or recommendation without a trusted source, or cite a source you have not read. Memory is not a source.
  • Change one artifact and leave the ones linked to it out of sync. Screens are linked artifacts: a component or token change that leaves a screen broken or stale is not done.
  • Write a ds-* class by hand in a screen, restyle a component from apps/docs/screens/screens.css, or reuse the original's visual design, images or logos in a screen (apps/docs/screens/AGENTS.md).
  • Add a screen without its source, its licence text and the copyright notice the licence requires.
  • Fork component tokens per platform or per mode, or put a platform or mode value in a component token file: override the raw or semantic token in packages/tokens/src/platforms/<id>/, or the semantic token in packages/tokens/src/modes/<id>/, instead.
  • Scope a statement to a platform because its source is web-only when the statement holds everywhere, or state a platform behaviour without that platform's trusted source.
  • Create GitHub Pages, deploy the site anywhere, or add a deployment job to the workflows (agents/git.md).
  • Open an issue anywhere other than the ThalesGroup/quantum repository (agents/git.md).

Model and repository map

What the four artifacts, foundations, screens, token tiers, platforms and modes are, and where everything lives. Part of the agent guide: start from the root AGENTS.md.

Mental model

Four artifacts describe one component. They must always agree; pnpm check proves it.

Artifact Path Owns
Spec packages/components/specs/components/<id>.spec.json What the component is: role, anatomy (parts with roles), properties, states, layout, accessibility, content rules, examples, implementation metadata, and the decision log that led there. Technology-agnostic.
Component tokens packages/tokens/src/components/<id>.tokens.json Every design decision of the component (colors, dimensions, typography) as DTCG tokens aliasing semantic tokens.
Docs apps/docs/content/components/<id>.md Usage guidance for designers and developers: when to use, variants meaning, content, accessibility. Prose only; no data that the spec already holds.
Web implementation packages/components/src/components/<id>/<id>.css + <id>.template.mjs The reference rendering: vanilla CSS consuming only component tokens, and a pure HTML template render(props).

Foundations

Some decisions belong to no single component but hold for all of them: the building blocks of the system (color, typography, spacing and sizing, shape, elevation and effects, motion, iconography), global rules such as the focus ring, and code syntax colors; text selection is planned. A foundation is that global specification, documented like a component but with no anatomy, role or properties:

Artifact Path Owns
Foundation spec packages/components/specs/foundations/<id>.spec.json The semantic tokens it owns (tokens), what it is as one rule per aspect (rules), the components that must follow it (appliesTo), its contrast requirements (computed in every platform × mode by pnpm validate), its cited accessibility statements, the specimens that draw the tokens it is built from (raw palettes and scales included) on its page, examples rendered with the components following it (or code samples next to the spec, in packages/components/specs/foundations/<id>/), decisions[] and changelog. Schema packages/components/specs/schema/foundation.schema.json.
Foundation docs apps/docs/content/foundations/<id>.md Usage guidance, platform sub-headings, a sourced Accessibility section, like component docs.

A foundation owns semantic tokens only (none yet when no decision gives its primitives a meaning: elevation and effects); components follow it by aliasing them from their own component tokens, and never re-specify it. Its specimens present raw and semantic tokens by group pattern, so a new token appears on its page with no site change, and every raw and semantic token must reach one foundation (owned or in a specimen; pnpm validate warns otherwise). The site derives who follows a foundation from those aliases and renders each one under Foundations. Foundations carry no web file today: the components draw what they specify. A foundation that needs global CSS (::selection, code blocks) will add it under packages/components/src/foundations/, imported from packages/components/src/index.css.

Screens

A screen is a real-world page (a login page, an application shell, a dashboard) taken from an open-source project whose licence allows it and rebuilt only with the components and tokens of the design system. Screens prove that components hold together, list what the system still lacks (each screen's gaps, with their issues when some track them), and show the system on pages people recognise:

Artifact Path Owns
Screen apps/docs/screens/<source>/<block>.screen.mjs meta (name, section, description, changes from the original, design system gaps with their issues when some track them) and a pure render() that composes the components' own templates.
Source apps/docs/screens/<source>/source.json + its licence file Where the screens come from: project, url, repository, commit, licence and copyright, copied verbatim. Shown on every screen.
Layout apps/docs/screens/screens.css, apps/docs/screens/lib/ Layout only, on semantic tokens; shared building blocks (shells, forms, charts).

Screens are consumers of the design system, like the docs site: every change to a component or a token updates the screens that use it in the same change (agents/workflow.md, agents/impact.md, apps/docs/screens/AGENTS.md).

In Figma

@quantum-ds/figma (packages/figma/) draws the components in Figma. Each component has a Figma blueprint (packages/figma/src/blueprints/<id>.mjs): its Figma layer tree, read from the web implementation, naming the token slot behind each field and holding no value. The plugin recreates the tokens as Figma variables (one collection per tier, the modes on the semantic one, aliases kept) and expands each blueprint over every configuration of the spec, binding in each variant the token the active-token rule picks, the one the CSS reads. Like the screens, a blueprint is a consumer kept in step with its component: pnpm impact lists it, pnpm test fails when a token slot is neither bound nor explained (packages/figma/AGENTS.md, agents/token-sources.md "Drawing a component in Figma").

In Penpot

@quantum-ds/penpot (packages/penpot/) draws the components in Penpot from the Figma blueprints of @quantum-ds/figma (packages/figma/src/blueprints/<id>.mjs) and the same engine: nothing about a component is written for Penpot. It writes the Penpot token file the token build publishes (packages/tokens/dist/penpot/tokens.json: one set per tier group and per mode, one theme per mode, references kept) into the file's tokens, and translates each variant's Figma fields to the Penpot token properties that drive them. What Penpot cannot bind is set to the token's value and listed with the reason (packages/penpot/AGENTS.md, agents/token-sources.md "Drawing a component in Penpot").

In Bootstrap and shadcn/ui

@quantum-ds/bootstrap-theme (packages/bootstrap-theme/) and @quantum-ds/shadcn-theme (packages/shadcn-theme/) bring the design system to two web frameworks as themes, built from the tokens and nothing else: each one is a mapping (src/mapping.mjs) from the framework's own theming API to Quantum token paths, that its build turns into what the framework consumes (Bootstrap's root custom properties and Sass configuration; shadcn's CSS variables and registry theme item), in light and dark. The mapping respects the tiers: the framework's semantic layer (surfaces, text, borders, theme colors, type, radii, spacing, focus) reads Quantum semantic tokens; its per-component layer (button sizes, control borders, card surfaces) reads Quantum component tokens. A theme never restyles the framework's components: what the API cannot express is listed in the package README, not forced. Each package builds a self-contained dist/demo.html showing every component of the framework with the theme, to compare with the reference rendering (packages/bootstrap-theme/AGENTS.md, packages/shadcn-theme/AGENTS.md).

Tokens form three tiers with a strict reference direction:

raw (hidden primitives)        <-  semantic (what components read)        <-  component (per part)
packages/tokens/src/raw/**         packages/tokens/src/semantic/**             packages/tokens/src/components/<id>.tokens.json
                                   packages/tokens/src/additions/semantic/**

Raw is the Figma library's color palettes and palette switch, which nobody reads directly. Semantic is what components and the site read: the Figma library's mapped colors and its Dimension, Effect and Typography scales (the library applies those directly; there is no semantic layer of sizes or text styles on top of them), plus the few tokens the design system adds.

The raw and semantic tokens come from two places, kept apart (agents/token-sources.md): packages/tokens/src/raw/, packages/tokens/src/semantic/ and packages/tokens/src/modes/dark/ are recreated from the Figma library as it is (paths made valid, same values, same aliases, errors included); packages/tokens/src/additions/ and packages/tokens/src/platforms/ hold the few tokens the design system adds, and only those the library lacks to render the components.

scripts/build-tokens.mjs compiles the tiers to CSS custom properties in packages/tokens/dist/css/ (generated, not committed, never edited by hand), with an index.css entry point importing every file in cascade order; Style Dictionary (packages/tokens/style-dictionary.config.mjs) builds the published formats (CSS, Sass, JavaScript, Android, Figma, Penpot, Tokens Studio, DTCG) next to it. The site in apps/docs/ is a viewer: it renders what the four artifacts say and must never encode design knowledge of its own. It is also built with the design system: its chrome reads semantic tokens only and renders the design system's components wherever it shows a control they cover, so a new token or a new component can change how the docs look (apps/docs/AGENTS.md, enforced by pnpm validate).

Platforms

Everything above is written once for every platform. packages/components/specs/platforms.json names the platforms (web, ios, android, desktop) and the input modalities (pointer, touch, keyboard). A state, an example, a keyboard entry, an accessibility guideline or a content rule carries an optional platforms list when it applies to some platforms only; a state carries inputs when only some inputs produce it (hover needs a pointer, focus-visible a keyboard). Docs scope a paragraph with a ### <Platform name> sub-heading. Raw and semantic tokens may be overridden per platform in packages/tokens/src/platforms/<id>/; the build resolves every tier per platform and mode into packages/tokens/dist/platforms/<id>/<mode>.tokens.json, a DTCG tree any technology can transform. Component tokens are never forked: the decisions are the same everywhere, only the values differ. A technology (Flutter, React Native, Qt, JavaFX, a web framework) is not a platform: it targets one or more platforms and consumes their outputs. The web implementation is the only reference rendering; other platforms record their implementation under implementations.<id>.

Modes

A mode is a context in which the same token paths take other values: today the color modes light (the default) and dark, declared under modes in packages/tokens/tokens.config.json. The default mode is the base tiers; every other mode is an overlay of the semantic tier in packages/tokens/src/modes/<id>/ (same paths, aliasing raw tokens, no new path). Component tokens are never forked per mode either: a component's color decisions hold in every mode, only the semantic colors they alias change. Each mode carries a scheme (light or dark) that tells every technology which system appearance selects it. Modes and platforms combine: every platform has one resolved tree per mode; a token varies per platform or per mode, never both. On the web, the data-theme attribute sets the mode of an element and its subtree: light, dark, or auto (follows prefers-color-scheme); without it, the default mode applies (agents/conventions.md).

Repository map

AGENTS.md                      the agent guide (rendered on the site's "How it works" page)
agents/                        the topic guides AGENTS.md routes to
  agents/design-tools.json     the design tools the exports target, and how far their releases are reviewed
CLAUDE.md                      imports AGENTS.md
.claude/skills/                agent skills for recurring workflows
.githooks/commit-msg           Conventional Commits check, installed by pnpm install
.github/workflows/             CI (check, commits, impact, build) and the npm release workflow
package.json                   pnpm scripts (AGENTS.md "Commands"); pnpm workspace: packages/* and apps/*
scripts/                       build-tokens, validate, impact, specs-bridge, new-component, commit-lint, figma-library, tests -> scripts/AGENTS.md
  scripts/lib/paths.mjs        the layout map every script and the docs site read their paths from
packages/tokens/               @quantum-ds/tokens -> packages/tokens/AGENTS.md
  tokens.config.json           tiers, platforms, modes, outputs, syntax theme
  src/raw, src/semantic        recreated from the Figma library (agents/token-sources.md)
  src/additions/               raw, semantic and mode tokens the design system adds
  src/components/<id>.tokens.json  component tokens
  src/platforms/<id>/          per-platform overrides of raw and semantic tokens
  src/modes/<id>/              per-mode overrides of semantic tokens (dark)
  style-dictionary.config.mjs  the distribution formats (Style Dictionary)
  dist/                        GENERATED, not committed (pnpm tokens:build)
  dist/css/                    GENERATED CSS custom properties: raw.css, semantic.css, components/<id>.css, index.css (entry point)
  dist/platforms/<id>/<mode>.tokens.json  GENERATED resolved DTCG tree per platform and mode
  dist/shiki-theme.json        GENERATED syntax highlighting theme (Code foundation)
  dist/tokens*.css, *.scss, tokens-es6*, variables-css.js, resources*.xml, figma/, penpot/, tokens_studio/, dtcg/
                               GENERATED distribution formats (style-dictionary.config.mjs)
packages/components/           @quantum-ds/components (publishes specs/ only) -> packages/components/AGENTS.md
  specs/                       component.schema.json + one spec per component -> packages/components/specs/AGENTS.md
    specs/foundations/         global specifications shared by every component, foundation.schema.json
    specs/a11y/                accessibility registry: trusted sources, WCAG 2.2 (generated), role profiles
    specs/platforms.json       platform and input vocabulary
  dist/                        GENERATED, not committed: the published specs (pnpm components:build)
  src/                         web reference implementation, not published (the docs site imports it by relative path)
  src/index.css                entry point of the vanilla CSS reference implementation (fonts, foundations, components)
  src/fonts.css                webfonts named by the font-family tokens (Google Fonts)
  src/foundations/             global CSS of a foundation (code.css)
  src/components/<id>/         hand-written CSS + HTML template
  src/lib/template-utils.mjs   escaping helpers + icon ligatures
packages/figma/                @quantum-ds/figma: the Figma plugin drawing every component (publishes dist/) -> packages/figma/AGENTS.md
  build.mjs                    pnpm figma:export and the package build: writes dist/ (GENERATED, not committed)
  src/plan.mjs, src/plugin.js  the plan (variables, variants) and the plugin that draws it in Figma
  src/blueprints/<id>.mjs      the Figma layer tree of each component, read from its CSS and template (Penpot draws it too)
packages/penpot/               @quantum-ds/penpot: the Penpot plugin drawing every component -> packages/penpot/AGENTS.md
  build.mjs, serve.mjs         pnpm penpot:export (writes dist/, GENERATED, not committed) and pnpm penpot:serve
  src/plan.mjs, src/plugin.js  the plan (tokens, variants translated from the Figma plan) and the plugin that draws it in Penpot
packages/bootstrap-theme/            @quantum-ds/bootstrap-theme: the Bootstrap 6 theme built from the tokens -> packages/bootstrap-theme/AGENTS.md
  build.mjs, src/mapping.mjs   pnpm bootstrap-theme:build: the Bootstrap token -> Quantum token mapping, written to dist/ (GENERATED, not committed) with demo.html
  demo/                        the source of the demo page (every Bootstrap component, light and dark, theme on/off)
packages/shadcn-theme/               @quantum-ds/shadcn-theme: the shadcn/ui theme built from the tokens -> packages/shadcn-theme/AGENTS.md
  build.mjs, src/mapping.mjs   pnpm shadcn-theme:build: the shadcn variable -> Quantum token mapping, written to dist/ (GENERATED, not committed) with demo.html
  demo/                        the source of the demo page (a Vite + React + Tailwind app showing every shadcn component, light and dark)
apps/docs/                     the docs site (Astro) -> apps/docs/AGENTS.md
  content/components/, content/foundations/   one Markdown page per component and foundation -> apps/docs/content/AGENTS.md
  screens/                     real-world screens built with the design system, their sources and licences -> apps/docs/screens/AGENTS.md
  src/                         the viewer: pages, components, lib, styles

Change workflow

The change loop, where each kind of request goes, the decision loop, and the definition of done. Part of the agent guide: start from the root AGENTS.md.

The change workflow

Every request follows the same loop. Do not skip steps or stop halfway: a change that touches the spec but not the tokens, CSS, or docs leaves the repository inconsistent.

  1. Read first. Open the spec, the component tokens, the CSS and the docs of the component. For a new component, read packages/components/specs/components/button.spec.json and its siblings as the reference implementation of the pattern.
  2. Map the impact. List every artifact linked to what you are about to change (agents/impact.md). The change is the whole set, not the file the request names.
  3. Locate the decision. Decide which layer the request changes (table below). Prefer the highest layer that expresses the intent: a change that should apply to every control belongs in semantic tokens, not in a component's tokens.
  4. Edit in order: spec → component tokens → pnpm tokens:build → CSS/template → Figma blueprint (packages/figma/src/blueprints/<id>.mjs, drawn in Figma and in Penpot: a new part, layout or token slot changes its layer tree; every slot is bound or listed in notDrawn) → docs. Bump version in the spec and add a changelog entry (newest first) for anything visible. A stated design decision is also recorded in decisions[] (see "Decisions" below). Anything touching accessibility follows agents/accessibility.md: verify the source, then cite it.
  5. Update the screens. Every screen rendering a changed component, or a component whose tokens alias a changed token, is part of the change (pnpm impact lists them): apply any template API change to it, drop a gap a new component or token now fills, and look at it.
  6. Verify: pnpm check, then pnpm impact. Fix every error. Read warnings; they usually point at forgotten states, unused tokens or uncited references. Walk the impact checklist: every linked artifact is either updated or consciously left as is.
  7. Look at it: pnpm dev, open the component page, check the examples, the states matrix, the playground (and its token inspector: which token paints which part) and the accessibility contract on the Specification tab; then the Screens using it, at every frame width and in every mode.
  8. Report what changed in each artifact, the sources behind any accessibility statement, and every linked artifact you deliberately left unchanged, with the reason.

Where does a change go?

Request sounds like Change
"Add a success intent" Spec: new option on intent, example. Semantic tokens: the Figma library already maps global.bg.<emphasis>.functional.success.*, global.text.on-<emphasis>.functional.success.default, global.border.<emphasis>.functional.success.*; add a role only if it is missing, in packages/tokens/src/additions/semantic/. Component tokens: <id>.<part>.<property>.<emphasis>.success.<state> for every affected part/property/emphasis/state. CSS: one ladder rule per emphasis for [data-intent="success"]. Docs: row in the Intent table. Contrast computed for every new pair.
"The border must be the primary color" Semantic tokens if it applies to all controls, otherwise component tokens: repoint <id>.container.border-color.* aliases. Rebuild. CSS unchanged (it only reads tokens). Docs: mention if the meaning changed.
"Buttons are too small on mobile" The heights come from the Figma library's space.* in each component's tokens, the same on every platform; there is no control height token (an earlier decision removed them). A per-platform height is a decision to discuss first.
"Add a loading state" Spec: state loading (trigger system), possibly property loading and a part indicator. Tokens for the indicator. CSS: [data-state="loading"] plus the native mechanism. Template: render the indicator. Docs: row in States. Accessibility: how the busy state is announced, cited from APG/WAI-ARIA (agents/accessibility.md).
"Make it accessible" / "What does a screen reader say?" Read the role profile in packages/components/specs/a11y/roles.json and its APG pattern first. Answer and specify only from trusted sources (agents/accessibility.md); cite them in the spec, explain them in the docs.
"Every component must…" / "The focus ring…" / "Define the code colors" A foundation (agents/model.md): the semantic tokens first, then packages/components/specs/foundations/<id>.spec.json (rules, specimens, contrast, accessibility, examples, decisions[]) and apps/docs/content/foundations/<id>.md. Components follow it by aliasing its tokens; appliesTo makes pnpm validate require it. Never re-describe it in each component.
"Add a new component" pnpm new:component <id> --name ... --role ..., then fill every generated file. Add the component to packages/components/src/index.css (the scaffold does it). If the role has no profile in packages/components/specs/a11y/roles.json, write it first from the APG pattern and the WAI-ARIA role. Then the docs: every site control the component covers becomes the component (pnpm validate lists them), and the table in apps/docs/AGENTS.md says so. Then its Figma blueprint: the scaffold writes a starter one (packages/figma/src/blueprints/<id>.mjs) that only draws the scaffolded CSS; rewrite its tree from the real CSS and template (packages/figma/AGENTS.md).
"Hover does not exist on touch" / "This state only exists on iOS" Spec: inputs on the state (pointer, touch, keyboard), or platforms when a platform genuinely lacks or adds the state. CSS and template only cover states that include web. Docs: say so in the States table.
"Support dark mode" / "This color is wrong in dark mode" Semantic tokens of the mode: packages/tokens/src/modes/dark/ (same path as in packages/tokens/src/semantic/, aliasing a raw token; add the raw primitive if missing), or packages/tokens/src/additions/modes/dark/ for a token the design system added. Never a component token. Rebuild. Every component aliasing it (directly or through semantic aliases): contrast recomputed in that mode, version + changelog with the ratios. Docs only if the guidance changes.
"Add a high-contrast / dim mode" A modes entry in packages/tokens/tokens.config.json (with its scheme) and its overlay directory packages/tokens/src/modes/<id>/. Rebuild: the web gets [data-theme="<id>"], every platform a <id>.tokens.json. Contrast of every component in the new mode.
"On iOS the target must be 44 pt" A decision first: there is no control height token to override. Then an override in packages/tokens/src/platforms/ios/, never a component token. Rebuild. Spec: a guideline scoped platforms: ["ios"] citing Apple HIG (agents/accessibility.md). Docs: a ### iOS sub-heading.
"Add a Flutter / Qt / JavaFX implementation" Not a platform. Pick the platforms it targets, consume packages/tokens/dist/platforms/<id>/<mode>.tokens.json (@quantum-ds/tokens/platforms/<id>/<mode>.tokens.json; one per mode, selected by the system appearance named by the mode's scheme; or packages/tokens/dist/css/ (@quantum-ds/tokens/css/index.css) for the web), record it under implementations.<platform> in the spec.
"Add a screen" / "Rebuild this page with the design system" apps/docs/screens/AGENTS.md: a source with a licence that allows it (source.json + the licence text), then apps/docs/screens/<source>/<block>.screen.mjs rendering the components' templates, laid out by apps/docs/screens/screens.css on semantic tokens. Every gap goes to meta.gaps (its text; the issues tracking it when some exist).
"Change how the site looks" apps/docs/ only, with semantic tokens and the design system's components. A meaning the semantic tier lacks becomes a semantic token (packages/tokens/src/additions/semantic/), never a site literal. Never solve a component styling problem in the site CSS.
"Plug the real tokens" / "Where does this token come from?" See agents/token-sources.md.
"Draw the components in Figma" / "Update the Figma file" pnpm figma:export (or pnpm figma:export <id>…) builds the plugin into packages/figma/dist/; run it in the Figma desktop app (agents/token-sources.md "Drawing a component in Figma"). A component that renders wrong in Figma is fixed in its blueprint, never in the Figma file: the next run would redraw it.
"Draw the components in Penpot" / "Update the Penpot file" pnpm penpot:export (or pnpm penpot:export <id>…) builds the plugin into packages/penpot/dist/, pnpm penpot:serve serves it; install and run it in Penpot (agents/token-sources.md "Drawing a component in Penpot"). It draws the Figma blueprints: a component that renders wrong in Penpot is fixed in its blueprint or in the Penpot translation (packages/penpot/src/plan.mjs), never in the Penpot file.
"Compare a component with its Figma export" / "Align the button with Figma" The local export in raw_content/components/ (agents/token-sources.md) is the candidate rendering; the repository stays the reference for the variants (axes and option names). List every difference in the pull request, then take them one at a time: show before and after on the site, the team decides, then the decision loop (below) and one commit. Map every raw value of the export to the semantic token that carries it (values a couple of RGB units apart are export noise); change only the component's tokens, never raw or semantic tokens, and ask before any addition. Keep the running list of departures from Figma in the pull request; when the last difference is decided, open one issue labelled upstream-figma-ds listing each departure with its values, the Figma variable to bind and the decision id, so the Figma library converges.

Decisions: defining a component one statement at a time

Components are defined incrementally: someone states a decision ("the button has a blue border using the primary color", "there are three variants", "no icon on the small size"). Each statement goes through the decision loop below, in full, before the next one. It applies to every component. What a component shows before a decision confirms it is provisional: a decision may replace provisional content without superseding anything.

  1. Understand it. Restate the decision as a rule. Ask before applying when it is ambiguous, contradicts an active decision, or would break WCAG 2.2 AA (agents/accessibility.md): say which criterion it breaks, with its source, and propose a compliant alternative.
  2. Store it in the spec's decisions[], newest first: a stable kebab id, date, version (the one this change ships in), area, status: "active", text (the rule, present tense), rationale only when one was given (never invented), tokens (the paths or group.* that carry it), platforms when scoped, refs when accessibility is involved (required for area: "accessibility"). A decision that replaces an earlier one lists it in supersedes, and the earlier one becomes status: "superseded"; old entries are never edited or deleted. The site renders the log on the component's Decisions tab.
  3. Tokens. Create, update or delete semantic tokens when the decision is a system-wide meaning (step 3 of the change workflow above), then component tokens for every affected part × option × state. Raw tokens only when a needed primitive is missing (agents/token-sources.md). Delete tokens nothing uses any more. pnpm tokens:build.
  4. Spec. Anatomy, properties and options, states, layout, content rules, examples[] (add or change an example when the decision is visible), accessibility contract. Bump the minor version (patch for a fix that changes no decision) and add a changelog entry naming the decision ids.
  5. Web implementation. CSS rules for every new option and state, template parts. CSS still reads only component tokens.
  6. Docs. Tables (variants, sizes, states), guidance, platform sub-headings. Docs explain why and when; they do not copy the decision log.
  7. Examples, states matrix, playground and screens. They are generated from examples[], properties and states: check on the site that they show the decision. Never patch the site for one component; if the viewer cannot show a decision, open an issue (agents/git.md). Update and look at every screen rendering the component (pnpm impact lists them).
  8. Accessibility check. For every affected pair, compute the contrast from the resolved tokens in every mode × option × enabled state (text 4.5:1, WCAG 1.4.3; borders that identify the control and the focus ring 3:1, WCAG 1.4.11) and record the ratios in the changelog entry. Also review target size (2.5.8), focus visibility (2.4.7), meaning by color alone (1.4.1), the role profile, and each platform's overlay. New statements cite sources you opened (agents/accessibility.md).
  9. Verify and look: pnpm check, pnpm impact, then the site: Decisions tab, examples, states matrix, playground, Specification tab.
  10. Report and commit. Report per artifact what the decision changed, the contrast ratios and sources, and each linked artifact left unchanged with the reason. Commit the decision on its own branch, as a Conventional Commit scoped to the component (feat(button): blue border in the primary color; agents/git.md), push the branch and open a pull request with the labels that fit and a description a reviewer can follow (agents/git.md "Pull requests").

Definition of done

  • Spec validates against the schema; version bumped and changelog entry added.
  • Every stated decision recorded in decisions[]; a replaced one marked superseded.
  • Component tokens cover every affected part × option × state; aliases point to semantic tokens.
  • pnpm tokens:build run (its outputs are not committed).
  • CSS has a rule for every option and every state; template renders every example.
  • The Figma blueprint follows the CSS structure and binds or lists every token slot (pnpm test, which draws it in Figma and in Penpot).
  • Docs updated (variants/sizes/states tables, content, accessibility).
  • Contrast of every affected color pair computed from the resolved tokens, in every mode, and recorded. A foundation's pairs are declared in its contrast[] and computed by pnpm validate.
  • A rule shared by every component lives in a foundation (packages/components/specs/foundations/), not in each component; components following it alias its tokens.
  • Every new raw or semantic token reaches a foundation page: owned by a foundation or matched by one of its specimens (no pnpm validate warning).
  • Every accessibility statement cites a trusted source you actually opened (agents/accessibility.md); the docs Accessibility section links the sources that back its explanations.
  • Anything platform-specific is scoped (platforms, inputs, ### <Platform>); universal statements really hold everywhere; platform overlays rebuilt.
  • pnpm check passes with no errors. Warnings understood.
  • pnpm impact passes; every unchanged linked artifact is justified in the report (agents/impact.md).
  • Component reviewed in the site (examples, states matrix, playground, accessibility).
  • Every screen using a changed component or token updated (API changes applied, filled gaps removed) and reviewed on the Screens tab, at every frame width and in every mode.
  • The docs site still uses only semantic tokens, and the components covering its controls (a new component replaced its site look-alikes; apps/docs/AGENTS.md table updated).
  • Committed on its own branch, never on main, as a Conventional Commit scoped to the component or area (agents/git.md "Commits").
  • Branch pushed and pull request opened with the labels that fit and a human-readable description (agents/git.md "Pull requests").
  • Final report: what was done, and what the reviewer must look at precisely (agents/git.md "Pull requests").

Conventions

Identifiers, roles, the component token path grammar, states, platforms, modes and the web implementation rules. Part of the agent guide: start from the root AGENTS.md.

Conventions

Identifiers

  • Component ids, part ids, option values, state ids: kebab-case (icon-start, focus-visible).
  • Property ids: camelCase (iconStart), because they become template props and, later, framework props.
  • One component = one id used everywhere: spec id, token root group, docs file, packages/components/src/components/<id>/, CSS class ds-<id>.

Roles (from Nathan Curtis' "component and part roles as composites")

  • A component role says what the component is (button, togglebutton, textbox, disclosure, alert, presentation…). It is a composite: it implies semantics, keyboard behaviour and ARIA for every platform, so implementations never re-derive them.
  • A part role says what a part does for the component (container, label, icon, indicator, description, errormessage, panel…).
  • Both vocabularies live as enums in packages/components/specs/schema/component.schema.json. Extending them is a deliberate act: edit the schema, explain the new role in packages/components/specs/AGENTS.md.
  • The first anatomy part is always the root and has role container.

Component token path grammar

<component>.<part>.<property>[.<option>][.<state>]
button.container.background.primary.hover
button.container.border-radius
button.label.typography.md
  • <part> must be a part id from the spec's anatomy.
  • <property> is a kebab-case CSS-like property name (background, border-color, min-height, typography).
  • <option> is an option value of an enum property (primary, sm); <state> a declared state id.
  • Which token applies to a configuration follows from the path alone: an option missing from the path matches any value, a state without a token falls back to default, the token naming the most options wins. Rule and checks: packages/tokens/AGENTS.md "Which token applies".
  • Values alias semantic tokens only. Never a raw token, never a literal.
  • The generated CSS variable is the path joined with -: --button-container-background-primary-hover. Typography composites expand to -font-family, -font-size, -font-weight, -line-height, -letter-spacing.

States vocabulary

default, hover, active, focus-visible, disabled, selected, checked, unchecked, indeterminate, pressed, expanded, collapsed, invalid, readonly, loading, dragging, visited, current. Every spec declares default. A state used in a token path must be declared in the spec.

Platforms and inputs

  • Vocabulary in packages/components/specs/platforms.json; the schema enums mirror it and pnpm check keeps them equal.
  • Default is universal. Add platforms only when a platform genuinely differs, and inputs only when a state depends on how people interact (hover, focus-visible). A statement whose source is web-only but whose content holds everywhere stays universal.
  • A platform statement cites that platform's trusted source (Apple HIG, Android developers, Material). Never infer a platform behaviour from another platform's documentation.
  • Docs: ### Web, ### iOS, ### Android, ### Desktop sub-headings, exactly those names.
  • Tokens: override in packages/tokens/src/platforms/<id>/, same paths as the base tokens, only raw and semantic tiers, no new paths. Lengths stay in the DTCG unit (px, read as pt or dp).
  • implementations.web is the reference rendering and stays required; ios, android and desktop are optional metadata (control, framework, source, notes).

Modes

  • Vocabulary in packages/tokens/tokens.config.json (modes): exactly one default (the base tiers), each with a scheme (light or dark). Overlays in packages/tokens/src/modes/<id>/ override semantic tokens only, same paths, aliasing raw tokens. A component token, a raw token or a new path in a mode is refused by the build.
  • Descriptions, docs and spec statements name modes by their meaning ("dark mode"), never by a technology mechanism; the web mechanism belongs to ### Web sub-headings and packages/components/src/.
  • Web: data-theme="<mode>" on any element switches its subtree; data-theme="auto" follows prefers-color-scheme; no attribute means the default mode. The generated CSS declares the semantic and component tokens on :root, [data-theme] so aliases resolve again in every context, and sets color-scheme. Component CSS never mentions modes.
  • Other platforms: one resolved tree per mode, packages/tokens/dist/platforms/<id>/<mode>.tokens.json; the technology selects the tree from the system appearance (the mode's scheme).

Web implementation

  • Root class .ds-<id> on the native element named in implementations.web.element. A form control with a label, a description or an error message as parts has a wrapper root and names the native control as implementations.web.rolePart (packages/components/AGENTS.md "Form controls with a wrapper root"); the accessibility checks then apply to that element.
  • Enum properties become data-<prop>="<option>" attributes; booleans become native attributes when one exists (disabled), otherwise data-<prop>.
  • Parts are .ds-<id>__<part>, optionally with data-part="<part>". Decorative parts carry aria-hidden="true".
  • Every state selector is written twice through :where(): the native pseudo-class and [data-state="<state>"], so the site can force any state for review.
  • CSS reads only --<id>-* tokens and private --_* properties. No literals.
  • The template is a pure function render(props) -> string with no DOM access, usable in Node and browsers.
  • No script ships in the web implementation: interactive behaviour comes from native HTML (<button>, popover, popovertarget). A pattern that needs script to be accessible (an ARIA menu's arrow keys) is not used; the docs site may use script for itself, never for a component.
  • A composite (Dropdown) renders its subcomponent part (component: "button") with that component's template, and lists repeated parts with their own properties behind a list property (packages/components/specs/AGENTS.md "Repeated parts and composite components").

Change impact

What else to update or review when an artifact changes. Part of the agent guide: start from the root AGENTS.md.

Change impact

Every change is propagated to everything linked to it, in proportion to its impact. pnpm impact computes the links from the diff (rules in the LINKS table of scripts/impact.mjs) and prints a checklist; pnpm check then proves the mechanical part of the consistency.

When this changes Also update or review
Spec Component tokens, CSS, template, docs; docs of components listing it in related; the spec and tokens of a component that renders the same as it (MIRRORS in scripts/lib/mirrors.mjs: the icon button mirrors the button); the screens rendering it. Version + changelog; decisions[] when a decision was stated. Its Figma blueprint (packages/figma/src/blueprints/<id>.mjs, which the Penpot plugin draws too) when a part, property or state changes. A new component: its starter Figma blueprint, rewritten from the CSS and template; the site controls it covers (apps/docs/src/, apps/docs/AGENTS.md table) and the screen gaps it fills (apps/docs/screens/). The framework themes' mappings (packages/bootstrap-theme/src/mapping.mjs, packages/shadcn-theme/src/mapping.mjs): a renamed or removed property, option, state or part fails their tests; a new one may deserve an equivalence.
Component tokens pnpm tokens:build; CSS if variables appear or disappear; the Figma blueprint, which binds every slot or lists it in notDrawn (pnpm test); docs if a meaning changed; the tokens of a component that renders the same as it (pnpm validate fails until they resolve to the same values); the screens rendering it; the framework themes' mappings (a removed token path fails their tests, a changed value shows in their demos). Version + changelog.
CSS or template Spec (version, changelog, implementations.web); docs if behaviour or rendering guidance changed; the Figma blueprint, whose layer tree follows the CSS structure; the CSS of a component that renders the same as it; the screens rendering it (a template API change is applied to each of them).
Docs The spec must say the same thing; every WCAG criterion the docs rely on is cited in the spec.
Foundation spec Its docs page; the semantic tokens it owns; the docs of every component following it. Version + changelog (pnpm impact fails otherwise); decisions[] when a decision was stated.
Foundation docs The foundation spec must say the same thing; every WCAG criterion linked is cited in it.
Foundation code sample (packages/components/specs/foundations/<id>/) The foundation spec: version + changelog if what the sample shows changed.
Raw or semantic tokens pnpm tokens:build; packages/components/src/fonts.css if a font family changed; every component aliasing them (directly or through semantic aliases): version if its appearance changes, docs if contrast or emphasis changes; apps/docs/src/styles/site.css when the docs chrome reads a changed semantic token (check the site in every mode); the screens rendering a component that aliases them, and apps/docs/screens/screens.css when it reads them; every foundation owning, measuring or presenting them (contrast recomputed by pnpm validate, version if it changes; its specimens redraw by themselves, a token no foundation presents is a warning). The framework themes' mappings when a semantic token path goes (their tests fail) or its value changes (rebuild their demos).
Platform overlay (packages/tokens/src/platforms/<id>/) pnpm tokens:build (packages/tokens/dist/platforms/<id>/*.tokens.json); every component aliasing the overridden tokens: version if its appearance on that platform changes, docs if the platform guidance changed.
Mode overlay (packages/tokens/src/modes/<id>/) pnpm tokens:build (packages/tokens/dist/css/semantic.css, packages/tokens/dist/platforms/*/<id>.tokens.json); every component aliasing the overridden tokens: contrast in that mode recomputed, version + changelog with the ratios, docs if the mode guidance changed; the screens using them, in that mode.
Distribution formats (packages/tokens/style-dictionary.config.mjs) pnpm tokens:build and a look at the changed outputs in packages/tokens/dist/; the list of outputs in packages/tokens/README.md, "Distribution formats" in packages/tokens/AGENTS.md, the package changelog.
Screens layout or helpers (apps/docs/screens/screens.css, apps/docs/screens/lib/) Every screen: look at it on the site at every frame width and in every mode.
Screen source (apps/docs/screens/<source>/source.json) Its licence file and every screen of the source (their pages show the source, licence and commit).
packages/components/specs/platforms.json Schema enums (platform, input), packages/tokens/tokens.config.json platform ids, apps/docs/src/lib/platforms.ts, agents/conventions.md "Platforms and inputs", packages/components/specs/AGENTS.md.
Schema Every spec, apps/docs/src/content.config.ts, the scaffold, the Specs bridge, the validator, packages/components/specs/AGENTS.md; the foundation schema reuses the component schema's definitions.
packages/components/specs/a11y/ registry Every spec and docs page citing it; the scaffold (role profiles); packages/components/specs/AGENTS.md.
packages/components/src/lib/template-utils.mjs Every template, every screen.
Scripts or libraries Their tests, scripts/AGENTS.md, the root AGENTS.md or the agents/ guide when a command or obligation changes, the site when it uses the library.
package.json scripts AGENTS.md "Commands" and .github/workflows/ci.yml.
A guide in agents/ The root AGENTS.md ("Read before acting" and the rules it summarises); the skills in .claude/skills/ that cite it by path or section name.
A skill (.claude/skills/<name>/SKILL.md) The "Skills" table of the root AGENTS.md.

pnpm impact fails when a component's spec, tokens, CSS or template, or a foundation spec, changed and its version did not move. Every other unchanged linked artifact is a review item: update it or say in the report why it stays as it is. In CI it runs on pull requests against the target branch.

Accessibility

Trusted sources, citations, role profiles and what pnpm check enforces. Part of the agent guide: start from the root AGENTS.md.

Accessibility

Accessibility statements (behaviour, rendering, keyboard, naming, usage rules) come only from trusted sources, and every statement cites them. The target is WCAG 2.2 level AA; AAA criteria may be cited as extra quality but are not mandatory.

  • Trusted sources are listed with their URL prefixes in packages/components/specs/a11y/sources.json: W3C (WCAG 2.2 and its Understanding/Techniques, WAI-ARIA, APG, AccName, HTML-AAM, Core-AAM, ARIA in HTML, WAI tutorials), WHATWG HTML, MDN, RGAA, EN 301 549, Apple and Android/Material platform guidelines, and the Orange accessibility guidelines. A URL outside these prefixes fails pnpm check.
  • Platform statements cite the platform's own trusted source: Apple HIG for iOS (and macOS), Android developers and Material for Android. A guideline with platforms is checked like any other; the site groups guidelines by platform.
  • Verify before citing. Open the page, confirm it says what you claim and that the anchor exists. If no trusted source supports a claim, reword it to what the sources say or drop it. Never cite from memory.
  • Spec contract. accessibility holds ariaRole, name, keyboard[] and guidelines[]; each carries refs: wcag:<sc> (resolved against packages/components/specs/a11y/wcag.json, generated from the W3C's own data) or the id of an entry in accessibility.references[]. Parts that are pure decoration are decorative: true.
  • Role profiles. packages/components/specs/a11y/roles.json states what a component role obliges: ARIA role, accessible name, keys, focusability, the WCAG criteria that must be cited, and the native web elements that carry the role. Each profile cites its sources. A role without a profile cannot be specified; write the profile first from the APG pattern and WAI-ARIA.
  • Docs. The Accessibility section explains why, and links the trusted sources behind each explanation. Every WCAG criterion it links must be cited in the spec.
  • Foundations. A foundation's accessibility.guidelines[] and contrast[] follow the same citation rules (no role profile: a foundation is not a component). Its contrast pairs are computed from the resolved tokens in every platform × mode and fail under their minimum.
  • What pnpm check enforces: uncited statements, untrusted URLs, unknown or removed WCAG criteria; the role profile (ARIA role, name, keys, focus-visible state, required criteria); the rendered HTML (native element with the role, native boolean attributes, aria-hidden on decorative parts, a non-empty accessible name); the CSS (:focus-visible for focusable roles, native pseudo-class next to every forced state, prefers-reduced-motion when anything animates); the docs (sourced Accessibility section consistent with the spec). Checks cannot prove everything (contrast of real token pairs, screen reader output): review those on the site and cite the criterion.

Where the tokens come from

The Figma library, its local export, the additions, the Figma component exports, how to plug another export, and how to draw a component in Figma. Part of the agent guide: start from the root AGENTS.md.

The local export

The Figma library is private. Its export lives in raw_content/ at the repository root, on the machines that have it: git ignores the directory, so it is never committed, never quoted, and never named in a spec, a token, the docs, the site or a commit message. Those say "the Figma library". Nothing in pnpm check or CI reads it: pnpm tokens:figma-library and the tests that read it skip without it. When the work needs it (a comparison with Figma, the origin of a token) and it is missing, ask for it rather than guessing. A new worktree starts without it: copy it from the main checkout.

Where the tokens come from

The raw and semantic tokens have two origins, kept in separate directories so that the origin of any token shows in its path:

  • The Figma library. Its design tokens export (raw_content/: Colors / Base, Colors / Palette Switch, Colors / Mapped light and dark, Dimension, Effect, Typography) is read-only reference material: nothing reads it at build time. scripts/figma-library.mjs --write recreated it once as clean DTCG: Colors / Base and the Palette Switch in packages/tokens/src/raw/ (hidden primitives), Colors / Mapped, Dimension, Effect and Typography in packages/tokens/src/semantic/ (read by components and the site), the dark mapping in packages/tokens/src/modes/dark/: the same paths, made valid only where a path or a CSS variable cannot carry them (⚠️ai → ai, semiBold → semi-bold, drop shadow → drop-shadow; brand.identity → brand.identity-base, whose CSS variable would collide with the mapped brand-identity), the same values (lengths in px, opacity as 0-1), the same aliases, errors and limitations included (typos, aliases to colors it does not contain, kept as literals, contrast failures recorded as known issues of the Color foundation). Colors the library composes from a step and an opacity are written as the resulting color. The Palette Switch is in its Ash mode; Slate is not recreated yet.
  • Additions. packages/tokens/src/additions/{semantic,modes/dark}/ and packages/tokens/src/platforms/ hold the few tokens the design system adds because the Figma library lacks them to render the components: line heights, letter spacing, motion, the icon font, transparent, the focus ring, and the iOS and Android font families. Each file says so in its $description. Nothing else is added: no semantic layer of sizes, radii or text styles over the library's scales. A new addition is a decision to ask for, never a convenience.
  • Component exports. raw_content/components/<id>-variants-raw.json is the Figma export of a component set of the Figma library, made with figma-cli: its variant properties and their defaults, then every variant with its auto layout (padding, gap, radius, size), fills, strokes and children (label text, icon instances). It holds no tokens: scripts/figma-library.mjs does not read it and nothing reads it at build time. Like the rest of the export, it is read-only and local. It is a candidate rendering a component is compared with, one difference at a time, through the decision loop (agents/workflow.md "Compare a component with its Figma export"). Cite what it shows (values, variant names) as "the Figma prototype", never its path.

These files are the starting point the system evolves from, by decisions (agents/workflow.md): edit them like any token file. pnpm tokens:figma-library reports every token of the library that has since been added, removed or changed; --write would undo that evolution, so it is not run again.

Plugging another export

  1. Drop the exported files into a tier directory (any file names, any nesting; every *.json under a tier's sources in packages/tokens/tokens.config.json is loaded and deep-merged). Delete the files they replace. Figma exports lengths as $type: "number" and opacity as a percentage: convert them to dimension ({ "value", "unit": "px" }) and to a 0-1 number, or the CSS gets unitless lengths. scripts/figma-library.mjs shows one way to do it.
  2. If the export puts raw and semantic tokens in one file, split them by top-level group into the two directories, or point both tier sources in packages/tokens/tokens.config.json at distinct subfolders.
  3. Run pnpm tokens:build. The only coupling between the tokens and the components is the set of semantic paths referenced by packages/tokens/src/components/*.tokens.json. The build lists every unresolved alias; repoint those aliases to the equivalent semantic tokens.
  4. Supported value shapes: legacy strings ("#2563eb", "16px") and DTCG 2024+ objects ({ "colorSpace", "components", "alpha", "hex" }, { "value", "unit" }), $type inherited from groups, composites typography, shadow, border, and aliases anywhere, including inside composites. Add a case to scripts/test/dtcg.test.mjs if you extend it.
  5. Font families: packages/components/src/fonts.css loads every family the tokens name from Google Fonts. If the tokens name another family, update its URL (the validator checks it).
  6. Platform values: anything that differs per platform (system font family, touch target heights) goes to packages/tokens/src/platforms/<id>/ with the same paths as the base tokens. The build refuses an overlay that adds a path or touches a component token.
  7. Modes: Figma exports one file per mode of a collection (light.tokens.json, dark.tokens.json). The default mode's file goes to packages/tokens/src/semantic/, every other one to packages/tokens/src/modes/<id>/ with a modes entry in packages/tokens/tokens.config.json (scheme, sources). Keep only the semantic tokens whose value differs from the default mode; the build refuses a mode that adds a path, overrides a raw or component token, or overrides a token a platform overlay also overrides.

Drawing a component in Figma

pnpm figma:export [id…] goes the other way: from the repository to a Figma file. It builds the Figma development plugin of @quantum-ds/figma into packages/figma/dist/ (not committed; pnpm build builds it too; run it again after any change to a spec or a token). In the Figma desktop app, Plugins > Development > Import plugin from manifest… with packages/figma/dist/manifest.json, then run Quantum components in a design file: its window (built with the design system) shows the progress of each component and the warnings, and stays open until closed. The package's own rules are in packages/figma/AGENTS.md. Without ids it draws every component; with ids, those and the components their instances use.

  • Variables. One collection per token tier: Quantum / Raw (hidden from publishing, no scope), Quantum / Semantic (a Light and a Dark mode, from the token modes; a second mode needs a paid Figma plan) and Quantum / Components, holding only the tokens the drawn components reach. A variable is named after its token path (button/container/gap/medium), a typography token gives one variable per member (button/label/typography/medium/font-size), aliases stay aliases, and the web code syntax is the token's CSS variable. These are the repository's tokens, not the Figma library's own collections: the plugin never edits those. Running it again updates the values in place. The web values are exported: platform overlays are not.
  • Blueprints. A component is drawn from its blueprint, packages/figma/src/blueprints/<id>.mjs: its Figma layer tree, read from the web implementation, naming the token slot that drives each field (format in packages/figma/src/plan.mjs). A blueprint holds no value: in each variant, the engine binds the token the slot makes active for that configuration (activeTokens, scripts/lib/token-map.mjs), the one the CSS reads. A new component needs a blueprint (pnpm test fails without one; pnpm new:component writes a starter one, rewritten from the real CSS and template), and every token slot of a component is bound by its blueprint or listed in its notDrawn with the reason; a changed CSS structure changes the blueprint.
  • The component set. On a page named after the component: one variant per option of every enum property and per state (a state a property sets, such as disabled or checked, is a value of State, as in the spec's state list), plus the axes a blueprint adds when the layout depends on something else (the button's icon slots, whose padding differs). The default variant is the top-left one. Text properties carry the texts and icon glyphs (ligatures of the icon font, as on the web), BOOLEAN properties show the optional parts, focus rings are frames outside the border in focus-visible only, and a part that is another component (the dropdown's trigger) is an instance of it. A component set that already exists is kept as it is: delete it to draw it again, so that no instance loses its main component unnoticed.
  • What Figma cannot hold is set without a variable or left out, and listed in each blueprint's notDrawn: durations and easings (no variable type), a unitless line height (Figma reads a number variable as pixels, so it is set as a percentage), the icon font's fill, grade and optical size axes, values computed from a token (the outline radius, a check mark's size), and the widths the web takes from its container (a literal width).
  • Fonts. The plugin loads every family the tokens name. The icon font, Material Symbols Outlined, is not among Figma's fonts: install it on the machine (the variable font of google/material-design-icons) or icons are drawn as empty squares.

Drawing a component in Penpot

pnpm penpot:export [id…] does the same for Penpot. It builds the plugin of @quantum-ds/penpot into packages/penpot/dist/ (not committed; pnpm build builds it too) and pnpm penpot:serve serves it at http://localhost:4400, since Penpot loads a plugin over HTTP. In Penpot (2.18 or later: the plugin API of tokens and variants), open a file, open the plugin manager (Ctrl + Alt + P), install http://localhost:4400/manifest.json and run Quantum components: its window, the Figma plugin's window with Penpot's theme, shows the progress and the warnings and stays open until closed. The package's own rules are in packages/penpot/AGENTS.md. Without ids it draws every component; with ids, those and the components their copies use.

  • Tokens. The plugin writes packages/tokens/dist/penpot/tokens.json, the Penpot token file the token build publishes, into the file's tokens: the sets Foundation, Component, Mode/Light and Mode/Dark, and the themes Mode / Light and Mode / Dark, the default one active. It keeps the tokens the drawn components reach and every token they reference; a token is named after its path (button.container.gap.medium), keeps its Penpot type (spacing, sizing, borderRadius… from the property its path names) and its references, so switching the theme switches every component. A number or opacity value (1.5) is written as text ("1.5"): the token file keeps DTCG numbers, which Penpot's Tokens panel imports, but its plugin API takes only text for them. Running it again updates the values in place (a number the Tokens panel imported equals its text and is left as it is); a token of another type under the same name is left alone, with a warning.
  • Blueprints. Penpot draws the Figma blueprints (packages/figma/src/blueprints/), expanded by the same engine: the same variants, bound to the same tokens. The plan then translates each Figma field to the Penpot token property that drives it (a padding, a gap along the layout direction, each corner radius, the stroke width, a minimum size of a layout child, the typography token of a text). A blueprint change is a change of both drawings; nothing is written for Penpot alone.
  • The component. On a page named after the component: every variant drawn as a board with a flex layout, laid out in the Figma grid, made a component, then all of them combined into one component with variants whose properties are the Figma variant properties. Penpot components have no text or boolean properties: a copy overrides its texts and icon glyphs directly, and an optional part is shown or hidden as the boolean's default says, which a copy changes. Focus rings are rectangles outside the border in focus-visible only, and a part that is another component (the dropdown's trigger) is a copy of it. A component that already exists is kept as it is.
  • What Penpot cannot hold is set to the token's value, and dist/plan.json lists it per component (unbound, with the tokens, and approximations): an icon's font size (its size is a sizing token, which cannot drive a font size), the divider's thickness (a border width as a size), the minimum size of a component itself (Penpot honours it inside a layout only: the component is given that size when its content is smaller), a border on one side (a line along it, until the plugin API has per-side strokes), a baseline alignment (aligned to the end) and an arc (an open path). What Figma cannot hold either (durations, easings, the icon font's axes) is left out as in Figma (notDrawn).
  • Fonts. Penpot's fonts are its own catalog (Google Fonts, where Inter comes from) and the team's fonts: the plugin finds each family by name, and a text whose family is missing keeps Penpot's default font. The icon font, Material Symbols Outlined, must be found too (upload it as a team font if needed), or icons are drawn as empty squares; a warning says so in both cases.

Following the design tools

How the repository keeps up with the design tools it exports to (Figma, Penpot, Tokens Studio) and the token format they share (DTCG): what each export relies on, where each tool publishes its changes, how a review turns them into issues, and how a change that needs the real tool is checked by a person. Part of the agent guide: start from the root AGENTS.md. The skill design-tools-watch runs the review step by step.

Why

Every design tool export is written against what a tool did on the day it was written: the @quantum-ds/tokens formats for Figma, Tokens Studio and Penpot (packages/tokens/style-dictionary.config.mjs), and the plugins of @quantum-ds/figma and @quantum-ds/penpot, which draw the components (agents/token-sources.md). The tools keep moving: a release can break an export, fix a limit the code works around, or bring a feature worth using. None of them is an npm dependency, so Dependabot never sees them (it does follow Style Dictionary). The review is how those releases reach the backlog.

The registry

agents/design-tools.json lists one entry per tool:

  • exports: what the repository writes for it.
  • reliesOn: every behaviour of the tool an export depends on, in a line each. A release that touches one of them is a finding; one that touches none is not. Keep it true: a new feature used by an export adds its line in the same change.
  • sources: where the tool publishes its changes (release notes, changelogs, the plugin API's updates). Only these are read; a source that moves is updated here.
  • minimum: the oldest version the exports support, when there is one.
  • reviewed: { "through", "on" }, the last release reviewed (a version, or the date of the last release note for a tool without versions) and the day of the review. null until the first review.

reviewed moves forward only in the change that records a review, never by hand.

Workarounds in the code

Code that works around a tool's limit says so with a marker in its comment: upstream(<tool>): <the limit>, the tool being an id of the registry (several, comma-separated, when they share the limit):

// upstream(penpot): no baseline alignment; the end of the cross axis comes closest for one-line texts.

The marker goes where the workaround is decided (a table, a fallback, a conversion), once. It is how a review finds what a release may let the code drop: git grep -n "upstream(" lists them, and pnpm test checks that every marker names a tool of the registry. A workaround added later gets its marker in the same change.

A review

The skill design-tools-watch runs it, for one tool or all of them, regularly (once a month, or before a release of a package) and once at least when a tool announces a major version.

  1. Read every source of the tool from the release after reviewed.through up to the latest stable one (the last twelve months when reviewed is null). Betas and release candidates are read too, and reported apart as what is coming.
  2. Match each change against reliesOn and the upstream(…) markers. A change matching neither is not a finding; the review only counts them.
  3. Classify each finding:
    • Breaking: an export stops working or loses something (a type renamed, an API removed).
    • Lifts a workaround: a marked limit is gone; the workaround can go, or be kept for the versions before (then minimum decides).
    • Fixes a known issue: an open issue of the repository is fixed upstream.
    • Opportunity: a feature that would let an export draw or bind more, or more simply.
    • Coming: any of the above, in a beta or a release candidate only.
  4. Check what can be checked here: read the tool's source when it is open (Penpot, Tokens Studio), run the token build and read its output, teach the plugins' stand-ins the new rule and run pnpm test. Each finding says how sure it is: confirmed (read in the tool's source or proven by a test) or to check in the tool (only the release note says it).
  5. Report the findings, numbered, each with its source link, its class, the artifacts it touches and how sure it is. Then, once the person who asked for the review agrees, one issue per finding that needs work (skill backlog), searching first for an issue that already covers it. Findings with no work (a fix for a problem the exports never had) stay in the report.
  6. Record the review: reviewed of each tool reviewed, and any new reliesOn line, in one commit on its own branch (docs(agents): review the design tool releases through <date>).

A check in the real tool

An agent cannot run Figma or Penpot. When a finding needs the real tool to be confirmed or ruled out, its issue does not claim the impact: it says so, gives the person who will run it everything needed to do it in one go, and is assigned to that person (the one who asked for the review, unless they name someone else). Its body has a Check in section:

  • Needs: the tool and version (a beta when the change is not released yet), the plan when it matters (a second variable mode needs a paid Figma plan), the fonts to install.
  • Setup: the commands, from a clean checkout of the branch to check (pnpm install, then pnpm figma:export <id> or pnpm penpot:export <id> and pnpm penpot:serve), and how to load the plugin or import the token file (agents/token-sources.md).
  • Steps: numbered, each one action, with the component, variant, token or panel to open.
  • Look at: what shows the impact (a variable's type, a token's value in the Tokens panel, a warning in the plugin window, a layer's binding), and what it should be if the export is right.
  • Answers: what result confirms the impact and what rules it out.
  • Report back: in a comment, the tool's version, what was seen (screenshots, the plugin window's warnings), and the answer.

When the result comes back, the issue is updated with it: confirmed, it becomes ordinary work; ruled out, it is closed with the result. A stand-in rule taught from a release note is kept only once the tool has confirmed it (packages/figma/AGENTS.md, packages/penpot/AGENTS.md).

Specs interoperability

Export to and import from Nathan Curtis' Specs format. Part of the agent guide: start from the root AGENTS.md.

Specs interoperability

Nathan Curtis' Specs format (npm @directededges/specs-schema, CC BY 4.0) describes components extracted from Figma. This repository's spec stays the source of truth; Specs is an export target and an import source. scripts/lib/specs-bridge.mjs holds every mapping decision in tables at the top of the file.

  • Export writes a valid Specs Component plus a .quantum.json sidecar with what Specs has no slot for: accessibility, content, implementations, changelog, part metadata and tokens without a Specs style (focus outline, transition). Our property metadata and each token's exact path travel inside Specs $extensions under the io.quantum key.
  • States become a Specs prop named state (interaction and system states); a state set through a boolean property stays that boolean and appears in invalidPropCombinations. state is therefore a reserved property id.
  • Import with the sidecar restores the spec and tokens exactly. Without it (a Figma-generated file) it infers anatomy, properties, states and token paths, and leaves prose empty.
  • Upgrading Specs: the schema version is pinned exactly in package.json. To move, read the new ADRs in the Specs ADR index, bump the version, run pnpm specs:check, then adjust the tables in specs-bridge.mjs and the tests in scripts/test/specs-bridge.test.mjs until it passes.
  • A specs:check error on a valid spec is a bridge limitation, not a spec error: fix the mapping, never the spec.
  • Foundations (packages/components/specs/foundations/) are not exported: Specs describes components. Their tokens travel with every component that aliases them.

Repository, commits and issues

Branches, pull requests, Conventional Commits, labels and issues. Part of the agent guide: start from the root AGENTS.md.

Repository, commits and issues

The repository is the GitHub repository ThalesGroup/quantum.

  • Never commit to main: one branch and one pull request per development. Start every change from an up-to-date main on a new branch, commit there, push the branch to origin and open a pull request against main (see "Branches" and "Pull requests" below). Never push to main, never merge your own pull request and never enable auto-merge: a human reviews and merges. pnpm check must pass before every commit (AGENTS.md "Do not").

  • Conventional Commits. Every commit message follows Conventional Commits 1.0.0 (see "Commits" below). The commit-msg hook refuses any other message, and CI checks every commit of a pull request (the commits job).

  • CI. .github/workflows/ci.yml runs on every pull request: check (pnpm check), commits (pnpm commits:check --base), impact (pnpm impact --base) and build (pnpm build, which uploads the docs site as an artifact, and a dry-run pack of every package); check and build run again on every push to main and nextgen. .github/workflows/release.yml publishes an alpha version of a package to npm (dist-tag alpha) with trusted publishing when a tag @quantum-ds/<package>@<version>-alpha.<n> is pushed on a commit of nextgen; no GitHub release is created (CONTRIBUTING.md "Releases").

  • No deployment. Never create GitHub Pages and never deploy the site, anywhere: no deployment job in the workflows. pnpm build and pnpm dev are for local review only. The docs site will later be integrated into https://quantum.thalesdigital.io/ by the Quantum team.

  • Out-of-scope work becomes an issue. When a conversation raises a new feature, a bug or an enhancement that is not directly part of the current work, open an issue for it right away instead of doing it or leaving it in the chat: gh issue create --repo ThalesGroup/quantum --title "…" --body "…" --label <label>. The body stands alone: context, the artifacts involved (paths), what is expected. Report the issue URL.

  • Labels. On every issue and every pull request, pick every label that fits from the repository's existing set (gh label list --repo ThalesGroup/quantum):

    • Kind. A pull request takes the label of its commit type (feat, fix, docs, ci, test, perf, build, chore, refactor), plus dependencies when it changes dependencies. An issue takes bug, enhancement or question instead, never a commit type (docs aside, as an area).
    • Area, every one the change or the issue touches: site (the docs website's code, apps/docs/ and its screens), content (its guidance pages, apps/docs/content/), tokens (packages/tokens/), components (packages/components/: specs, CSS, templates), docs (README, CONTRIBUTING, the agent guides in agents/ and the skills; the same label as the commit type, there is no documentation label). Scripts and workflows take no area label: the kind says it (build, ci).
    • Design tool, every one the change or the issue concerns: figma (the Figma library, its export, the Figma token format, and @quantum-ds/figma in packages/figma/), penpot (the Penpot token format, its import, and @quantum-ds/penpot in packages/penpot/), tokens-studio (the Tokens Studio token format). A change to packages/figma/ takes figma, one to packages/penpot/ takes penpot, on top of its kind.
    • Framework, every one the change or the issue concerns: bootstrap (the Bootstrap theme, @quantum-ds/bootstrap-theme in packages/bootstrap-theme/), shadcn (the shadcn/ui theme, @quantum-ds/shadcn-theme in packages/shadcn-theme/). A change to a theme package takes its framework label on top of its kind.
    • Quantum NextGen. Every issue and pull request that concerns it (a pull request against nextgen, an issue about its packages, specs, docs site, guides or skills) always takes nextgen.

    The area is the part of the repository, not the commit scope: fix(docs): … on the site's code takes fix and site; docs goes only on what touches the repository's own docs. Do not invent labels on the fly; create a new one only when asked.

  • This repository only. Always pass --repo ThalesGroup/quantum. Never open an issue or a pull request, or comment on one, in any other repository, including upstream projects (Specs, Astro, W3C…): describe the upstream problem in an issue here instead.

Branches

  • One development, one branch: a component decision (agents/workflow.md "Decisions"), a foundation, a script, a docs site feature, a docs rewrite. Unrelated changes go to separate branches and pull requests.
  • Name it <type>/<scope>-<short-kebab-subject>, with the type and scope of its main commit (see "Commits"): feat/button-primary-border, fix/docs-token-filter, docs/agents-pr-workflow.
  • Branch from the latest main (git fetch origin && git switch -c <branch> origin/main). If main moves before the merge, bring it in (rebase or merge origin/main), re-run pnpm check and push again.
  • On a branch, check against main: pnpm impact --base origin/main and pnpm commits:check --base origin/main.

Pull requests

  • Open it with gh pr create --repo ThalesGroup/quantum --base main --head <branch> --title "…" --body "…" --label <label> [--label <label>…]. The title is the Conventional Commit header of the change (the main commit's header when there are several commits).
  • The description is written for a human reviewer who did not follow the conversation: plain sentences, no internal jargon left unexplained, no pasted tool output. Use these sections:
    • Why: the request or decision behind the change, in one or two sentences.
    • What changes: per artifact (spec, tokens, CSS/template, docs, site, scripts), what changed and the visible effect; decision ids, contrast ratios and the sources cited (agents/accessibility.md).
    • Left unchanged on purpose: every linked artifact pnpm impact listed that was not updated, with the reason (agents/impact.md).
    • How to review: the precise places to look (files and lines, site pages, examples, states, modes), and the judgement calls the reviewer must confirm or reject.
    • Checks: pnpm check and pnpm impact --base origin/main results.
    • Follow-ups: issues opened for out-of-scope work (links).
  • Keep the description true: when review feedback adds commits, update it.
  • When done, report in the conversation: what was done (branch, commits, pull request link, labels), and what the reviewer must review precisely, the same list as the "How to review" section.

Commits

<type>(<scope>): <subject>

[body: what and why, decision ids, contrast ratios]

[BREAKING CHANGE: what consumers must change]
  • Type says what the change is for the people consuming the design system:

    Type Use for Spec version
    feat A new or changed decision, component, property, option, state, token; a docs site feature; a new command minor
    fix A correction that changes no decision; a bug in a script or the docs site patch
    docs Prose only: apps/docs/content/, AGENTS.md, agents/, README none, or patch
    refactor · perf · style Restructuring, speed, formatting: nothing visible changes none
    test Tests only none
    build Token pipeline, build scripts, package manifests none
    ci .github/workflows/, git hooks none
    chore Housekeeping that fits nowhere else none
    revert Reverts an earlier commit (revert: <header of the reverted commit>) as reverted
  • Scope names what changed: the component id (button, icon-button) for a component change, the foundation id (focus) for a foundation change, several comma-separated when one decision spans components (feat(button,icon-button): color modes), otherwise an area: tokens, modes, platforms, foundations, specs, a11y, components (the components package), figma (the Figma package), penpot (the Penpot package), bootstrap-theme (the Bootstrap theme package), shadcn-theme (the shadcn/ui theme package), web (the web reference implementation), docs (the docs app and the guidelines), screens, scripts, agents, ci, deps, release. A component scope that does not exist fails the check; the list lives in scripts/lib/conventional-commits.mjs.

  • Breaking changes (a removed option, property or token path, a renamed part, anything that makes consumers change their code or design files) take ! after the scope and a BREAKING CHANGE: footer saying what to change, and bump the spec's major version.

  • Subject: imperative or a noun phrase stating the decision, no final period, header at most 100 characters. One decision, one commit (agents/workflow.md "Decisions").

  • Check a message before committing with pnpm commits:check --message "<message>". pnpm install installs the hook (git config core.hooksPath .githooks); merge commits and git's own fixup!/squash!/Revert "…" messages are let through. Commits made before the rule are not rewritten.