This directory codifies the structural contracts for design systems.
tokens.schema.ts re-exports the token contract that every tokenized brand under
design-systems/<brand>/ must satisfy. The canonical runtime copy lives in
packages/contracts/src/design-systems/token-schema.ts so daemon importers and
repo guards consume one schema. manifest.schema.ts is the
project contract for Design System Project packages. Every bundled catalog
entry now ships manifest.json, DESIGN.md, and tokens.css; the daemon keeps
DESIGN.md-only discovery solely as a compatibility path for older or
user-installed folders.
_schema/
βββ manifest.schema.ts β project manifest schema (TS, machine-enforced when present)
βββ runtime.schema.ts β structured component/intent contract re-export
βββ tokens.schema.ts β token schema re-export (TS, machine-enforced)
βββ defaults.css β A2 fallback values (CSS, human reference)
βββ AGENTS.md β this file
The TypeScript schemas are the source of truth. defaults.css is a
human-readable mirror of the A2 fallback fields in the token schema
and exists so that reviewers can scan real CSS without parsing a TS
array β drift between the two is enforced by the design-system: A2 defaults parity guard. Manifest shape is enforced by
scripts/check-design-system-manifests.ts for any
design-systems/<brand>/manifest.json that exists.
Design System Project folders use fixed v1 file names:
manifest.jsonβ machine-readable project entry.DESIGN.mdβ canonical design prose.tokens.cssβ canonical compiled tokens.design-tokens.jsonβ optional Design Tokens JSON derived fromtokens.css+source/token-contract.report.json.tailwind-v4.cssβ optional Tailwind v4@themeCSS derived fromtokens.css; it must not redefine source values independently.components.htmlβ optional standalone component fixture.assets/β optional brand assets.preview/β optional static preview pages.USAGE.mdβ optional agent-facing package guide.components.manifest.jsonβ optional rebuildable cache derived fromcomponents.htmlandtokens.css.fonts/β optional webfont files.source/β optional importer evidence (scanned-files.json,evidence.md,tokens.source.json,token-contract.report.json, andsnippets/INDEX.json).manifests/,components/, andrules/β optional structured runtime graph, declared atomically throughmanifest.runtime.
The manifest guard validates every bundled package through its manifest.json.
When rich fields are declared, paths must be safe and present, JSON indexes
must parse, and committed components.manifest.json files must match a fresh
derivation from components.html plus tokens.css. These fields are also
runtime inputs: catalog discovery consumes manifest metadata; prompt
composition consumes usage, tokens, component information, import mode, craft
bindings, and the manifest-derived pull index; package/static-file routes expose
only declared preview and source files. Legacy DESIGN.md-only folders remain
outside this manifest guard because they have no manifest to validate.
The runtime schema itself is canonical in
packages/contracts/src/design-systems/runtime-schema.ts and re-exported here so
the repository guard and daemon share schema versions and reference rules.
Every shared token answers two questions:
- Who decides the value? β the brand author (Layer A) or the schema author (Layer B-slot, when the brand has no opinion).
- What happens if the brand omits it? β required, fallback, or alias.
The four layers fall out of those answers:
| Layer | Who decides | If omitted | Examples |
|---|---|---|---|
| A1-identity | brand | guard fails | --bg, --fg, --accent, --font-display |
| A1-structure | brand | guard fails | type scale, --container-max, --section-y-* |
| A2 | brand (with fallback) | guard fails today; derive script fills tomorrow | --motion-fast, --success, --space-4, --font-mono |
| B-slot | brand or schema-suggested alias | guard fails β brand must declare, either as var(--sibling) (collapsed) or independent value (richer) |
--fg-2 β var(--fg), --surface-warm β var(--surface) |
Brand-specific tokens that fall outside the shared schema are tracked
as C-extensions in BRAND_EXTENSIONS (per-brand allowlist) or
BRAND_EXTENSION_PREFIXES (global prefix allowlist for whole families
like --tag-bg-*).
A2 conceptually means "optional with fallback" β but artifacts are
generated by agents pasting one brand's :root block into a single
<style>. There is no global stylesheet that loads alongside the
brand, so a missing --motion-fast resolves to nothing inside the
artifact and any transition: var(--motion-fast) rule silently breaks.
Until a future derive script lands and inlines defaults.css values
into every brand's tokens.css, the only safe contract is "every
brand must declare every A2 token". The design-system: A2 required tokens guard enforces that strictly.
After the derive script ships, brand authors only need to write the
A1 tokens (and any A2 they want to override); the script populates A2
slots from defaults.css. The guard contract does not change β every
final tokens.css still contains every A2 token β but the work
shifts from human author to script.
Same artifact-paste constraint applies to B-slot tokens. Shared
components reference richer tiers via var(--fg-2), var(--meta),
var(--surface-warm), var(--border-soft) β if a brand omits the
slot, those references resolve to nothing and the artifact silently
breaks.
The aliasTo field on each B-slot entry is the schema-suggested
default, not a runtime fallback. A brand with no opinion on the
richer tier copies the alias verbatim into its :root:
--fg-2: var(--fg); /* default brand: 2-level fg */
--surface-warm: var(--surface); /* default brand: 2-level surface */
A brand that does have the richer tier binds an independent value:
--fg-2: #3d3d3a; /* kami brand: dark warm */
--surface-warm: #e8e6dc; /* kami brand: warm sand */
Either form satisfies the design-system: B-slot required tokens
guard. The pre-derive-script contract is identical to A2: every
brand's :root declares every shared slot.
Brand-specific tokens start in BRAND_EXTENSIONS[brand]. They earn
promotion when a second brand needs the same name:
C-extension B-slot A2
(one brand declares it) (multiple brands declare, (every brand declares
some alias to a sibling) with a sensible default)
kami: --leading-display β schema: --leading-display β schema: --leading-display
aliasTo: var(--leading-tight) fallback: 1.1
Concrete promotion rules:
- C β B-slot when β₯2 brands declare a token of the same name
and there is a meaningful sibling to alias to for brands that
lack the richer tier. Move the entry from
BRAND_EXTENSIONStoTOKEN_SCHEMAwithlayer: "B-slot"andaliasTo: "var(--sibling)". - C β A2 when β₯2 brands declare a token of the same name
and a defensible cross-brand fallback exists (no aliasing
needed). Move to
TOKEN_SCHEMAwithlayer: "A2"and afallback, then mirror the value indefaults.css. - B-slot β A2 when a B-slot starts being independently bound by
β₯2 brands (instead of aliasing). Replace
aliasTowithfallbackand add a defaults.css declaration. - A2 β A1 is rare. It happens when the previously-defaultable
value turns out to be brand-determining β e.g. if a future brand
redefines
--motion-basefrom 200ms to 50ms because its identity is "instant", and that change ripples meaningfully through the brand voice. Drop thefallbackand reclassify.
Demotion (A β B β C) is not currently supported. A token that is
genuinely no longer needed should be marked @deprecated in the
schema for one release and then deleted from every brand's
tokens.css in the same PR.
Schema growth has a cost β every new entry forces every bundled brand to declare or alias the new name when the derive script next runs. Resist adding tokens that are:
- Component-internal: a
.btn-primarybackground offset that no other component will ever read. Inline the value in the component rule. - One-off: a single layout's hero crop ratio. Not a token.
- Speculative: "we might want a
--motion-slowsomeday." Add it the first time a real interaction needs it, not before. - Already expressible: a
--accent-tint-50that resolves tocolor-mix(in oklab, var(--accent), transparent 50%). Inline thecolor-mix(...)call until β₯2 components need the same tint with the same alpha, then promote to a token.
When you change the token schema:
- Run
pnpm guardand confirm the bundled catalog still passes every design-system sub-check. - If you added an A2 entry: also update
defaults.csswith the matching declaration, byte-equivalent to thefallbackfield. - If you renamed a token: bump every brand's
tokens.cssand the matchingcomponents.html:rootpaste in the same commit. Otherwise the drift guard will fail. - If you removed a token from
TOKEN_SCHEMAand the same name now appears in only one brand: add it to that brand'sBRAND_EXTENSIONSentry so the unknown-token guard does not fail.
The checked-in schema and guards enforce the final token contract today, but the following authoring-automation questions are intentionally not answered:
- How does the derive script source A1 values from
DESIGN.md? Some sections (color palette, type scale) parse cleanly; others (visual atmosphere, do's and don'ts) do not. A frontmatter or fenced-block convention will likely emerge. - What happens when a brand's
DESIGN.mdcontradicts itself? e.g. accents listed as both cobalt and indigo. The derive script will need a deterministic resolution (last-wins, manual override flag, or hard fail). - Are A2 fallback formulas stable when re-derived? Bit-for-bit reproducibility of the script's output is required so that running the script twice on the same input does not churn the brand token files.
These remain future work for a change that introduces
scripts/derive-tokens-css.ts; that script does not exist yet.