Adopting dspack for an existing design system
August 10, 2026 ยท View on GitHub
You have a design system โ components in code, tokens, house rules, opinions earned the hard way. This guide walks the complete path from that starting point to a locally validated dspack contract that AI agents can query (ds-mcp), generate under (dspack-gen), and render from (dspack-emit).
Every step is labeled with one of three modes, and the labels are the point:
| Label | Meaning |
|---|---|
| ๐ค Automated | A tool does this. Run the command. |
| ๐ Human review | A machine produced output; a person confirms it is true. |
| โ๏ธ Design decision | Nobody can extract this. It encodes a choice your team made. No tool in this ecosystem will pretend otherwise. |
The honest summary up front: the machine-extractable half of a contract (tokens, component props, bindings, themes, layout) is steps 1โ3 and takes minutes. The valuable half โ the governance that makes an agent behave like someone who has read your guidelines โ is steps 4โ9, and it is authored, not extracted. That is by design: rules carry rationales, and rationales are decisions. (DESIGN.md explains why the format draws the line there.)
The journey at a glance
| # | Step | Mode |
|---|---|---|
| 1 | Extract a snapshot from your codebase | ๐ค Automated |
| 2 | ๐ค Automated | |
| 3 | Review the snapshot against reality | ๐ Human review |
| 4 | Author component guidance | โ๏ธ Design decision |
| 5 | Define categories and tag components | โ๏ธ Design decision |
| 6 | Record patterns and anti-patterns | โ๏ธ Design decision |
| 7 | Name your intents | โ๏ธ Design decision |
| 8 | Write rules | โ๏ธ Design decision |
| 9 | Write worked examples and cross-link them | โ๏ธ Design decision (machine-validated) |
| 10 | Validate the contract | ๐ค Automated |
| 11 | Serve it to agents | ๐ค Automated |
| 12 | Generate, lint, and render | ๐ค Automated (rendering profile: โ๏ธ) |
Step 1 assumes a React + Tailwind/shadcn or Vue 3 + Vuetify 3 codebase
(what dspack-export
currently supports). For any other stack, skip to
Writing the snapshot by hand โ a valid
document needs only dspack and name, and the shadcn reference contract
was written entirely by hand.
Phase 1 โ the snapshot (minutes)
Step 1 ยท Extract a snapshot โ ๐ค Automated
npm install -g @aestheticfunction/dspack-export
cd /path/to/your/design-system
dspack-export init # detects conventions, writes dspack-export.config.json
dspack-export generate --config dspack-export.config.json
(Installing from a source clone โ npm install && npm run build && npm link
in a checkout โ works identically.)
You get a <name>.dspack.json (named from your package.json name, or a
placeholder if there isn't one) containing everything observable from
source: components with props (cva variant enums and defaults on React;
defineProps/emits/slots on Vue), tokens from CSS custom properties or an
imported DTCG token file, themes (dark-mode overrides), layout
(breakpoints), frameworkBindings (import paths), and metadata recording
what generated it. If your tokens live in Figma or Tokens Studio, export them
to a DTCG file and point the config's tokens field at it โ the import is
file-based; no tool integration or network involved.
Three properties of the output matter later:
- It is deterministic โ same input, same bytes โ so you can commit it and diff regenerations.
- It carries a non-semantic
metadata["x-bootstrap"]ledger recording which sections the tool generated (with content hashes) and which governance surfaces await your authorship. It affects nothing downstream; once your contract is fully yours, delete it โ after which the tool refuses to touch the file at all. - Regeneration never destroys your work โ guaranteed, not advised. The
workflow is: commit the snapshot, then enrich the committed copy. If you
run
generateagainst a file containing anything you authored (or any file without the ledger), it refuses, says why, and points you at--outfor writing a fresh snapshot elsewhere to compare by hand.
Step 2 ยท Set the version โ retired
Nothing to do: snapshots declare the current spec version ("dspack": "0.4") directly. (This step existed when the tool emitted the older v0.2
shape; the number is kept so later steps keep their names.)
Step 3 ยท Review the snapshot โ ๐ Human review
Extraction is heuristic; you are the ground truth. Read the generated file once, end to end, checking:
- Props: are enum values and defaults right? cva and docgen catch most cases; wrappers and re-exports can confuse them.
- Descriptions: they came from your JSDoc. A doc comment written for developers is often wrong as agent guidance ("renders a button" tells an agent nothing about when).
- Tokens: tier and naming as your team understands them, not just as CSS declared them.
- What's missing: components the extractor didn't find, deliberately or not. Note them; you can add entries by hand.
Fix what's wrong directly in the file. From here on, the file is yours.
Phase 2 โ the governance layer (the real work)
Everything in this phase is a โ๏ธ design decision. The tooling validates what you write; it never writes it for you. If a step feels like work only your team could do โ that's the feature. This is the institutional knowledge ("the old card is deprecated", "modals are for destructive confirmations only") that no artifact records and every new hire absorbs slowly.
A worked example runs through this phase: the shadcn reference contract was authored by hand, incrementally, in this repository's git history โ and its arc is the pattern to copy:
c918aa6โ the contract is born as vocabulary only (v0.1: components, tokens, bindings). Useful to agents already, zero governance.44f02e2โ a component (table) arrives with a pattern:data-table-with-row-actions, prose guidance about how tables carry actions.805732cโ "record-collection intent โ the data-table pattern's prose becomes law": the prose pattern is promoted into an intent (record-collection), typed rules enforcing what the prose said, and a worked example the rules point at.f405fceโ a fix commit linking an example to the rule that referenced it. (Cross-linking is easy to forget; step 9 covers it and the validator now catches dangling references.)
Prose first, law second. You do not need to write rules on day one โ a
contract whose governance is three sentences of whenToUse is already ahead
of a README nobody pastes into a prompt.
Step 4 ยท Author component guidance โ โ๏ธ
For each component that matters, fill in what the snapshot cannot know:
descriptionโ rewritten as guidance, not implementation notes.whenToUse/whenNotToUseโ the sentences your design reviews repeat.accessibilityโ role, required attributes, keyboard interactions, label requirements.compositionโ sub-components, what must/may nest where.constraintsโ contextual must/should notes with severity.
Field shapes: spec v0.2; working examples of every field: the shadcn contract.
Step 5 ยท Define categories โ โ๏ธ
v0.4 lets the contract define its own taxonomy โ dspack bakes none in. Add a
top-level categories registry (e.g. interactive, overlay) and tag
components with them; rules can then select by category ("no interactive
descendants inside a button") instead of enumerating components. While here,
tag props with propRole (flag/dimension/choice/slot/handler/content/state)
โ it tells generation tooling what a prop is for. Spec:
v0.4.
Step 6 ยท Patterns and anti-patterns โ โ๏ธ
patterns are preferred combinations ("form fields stack vertically with
labels above"); antiPatterns are things your team has deliberately ruled
out, each with a reason. Anti-patterns punch above their weight: an agent
told what not to do and why stops reproducing the mistake your team
retired two years ago. (Id convention: pattern, anti-pattern, component,
and category ids are plain kebab-case โ bare-input-collection, no
prefix; the schema rejects dots. The rule. and ex. prefixes belong to
rules and examples only.) These are prose โ they are also the raw material
rules get promoted from (see the worked example above).
Step 7 ยท Name your intents โ โ๏ธ
An intent is a named kind of surface your system knows how to govern:
destructive-action, record-collection, data-collection. Intents scope
everything downstream โ rules apply to intents; examples belong to intents;
callers declare an intent when they ask for generation. Start with the one
or two situations where your team has the strongest opinions.
Step 8 ยท Write rules โ โ๏ธ
Rules are typed, deterministic predicates โ four types (spec v0.3 ยง5, v0.4 ยง4):
| Type | Enforces |
|---|---|
component-choice | which components must / must not appear for an intent |
required-composition | structure every instance of a component must contain |
forbidden-composition | structure and values no instance may contain |
required-props (v0.4) | named props a component must carry directly |
Every rule carries a rationale โ shown verbatim to whoever (or whatever)
violates it โ and a severity (must/should). Write the rationale first;
if you cannot say why, it is not yet a rule, it is a habit. Note for
required-composition: the required ids must be declared in the contract
(as components or sub-components), and a declared id satisfies nothing by
itself โ the rule checks actual descendants at lint time.
Step 9 ยท Worked examples โ โ๏ธ (machine-validated)
An examples[] entry embeds a complete dspack surface โ a small
component tree in your contract's vocabulary
(surface schema) โ showing what
good looks like for an intent. They serve double duty: few-shot exemplars
for generation, and living proof your rules are satisfiable.
The minimal skeleton โ the surface's four top-level fields are required and
system must equal your contract's name:
{
"id": "ex.minimal-signup",
"intent": "signup-form",
"description": "The smallest correct signup: framed fields, one primary action.",
"surface": {
"dspackSurface": "0.1",
"system": "your-system-name",
"intent": "signup-form",
"root": {
"component": "card", "id": "signup-card",
"children": [
{ "component": "input", "id": "email" },
{ "component": "button", "id": "submit", "text": "Sign up",
"props": { "variant": "default" } }
]
}
}
}
Two mechanical requirements the validator enforces:
- every example surface must pass S1 (surface schema) and S2 (uses only your contract's vocabulary โ component ids, props, enum values);
- cross-link rules and examples (
rule.examples: ["ex.your-example"]), and every reference must resolve.
Writing the snapshot by hand
No supported extractor for your stack? Start from
{ "dspack": "0.4", "name": "your-system" } and add components and tokens
as you go โ every other block is optional, and the phases above apply
unchanged. The shadcn contract is the
reference for what each block looks like fully populated.
Phase 3 โ validate and use
Step 10 ยท Validate โ ๐ค Automated
npx -p @aestheticfunction/dspack-spec dspack-validate --file your-system.dspack.json
Or, from a checkout of this repository:
git clone https://github.com/aestheticfunction/dspack
cd dspack && npm ci
npm run validate -- --file /path/to/your-system.dspack.json
Both are the same validator โ the published bin is a front-end over this repository's harness, never a fork. You get, per document: schema validation for its declared version, back-compat (the additive guarantee), governance consistency (unique ids; intent, component, and example references resolve; every example surface passes S1 + S2), and categories consistency. Fix what it reports; re-run until green.
Step 11 ยท Serve it to agents โ ๐ค Automated
npm install -g @aestheticfunction/ds-mcp
ds-mcp --dspack ./your-system.dspack.json
Connect Claude Code, Cursor, Claude Desktop, or Copilot
(client setup) and the
agent can query all eleven tools against your contract โ components, tokens,
patterns, anti-patterns โ and lint its own output with validate-ui. The
generation tools need the v0.3+ governance blocks from Phase 2.
Step 12 ยท Generate, lint, render โ ๐ค (rendering profile: โ๏ธ)
Every dspack-gen command takes your contract directly โ no registration, no fixtures:
npm install @aestheticfunction/dspack-gen
npx dspack-gen context --dspack ./your-system.dspack.json --intent your-intent
npx dspack-gen lint --dspack ./your-system.dspack.json --surface ./some.dsurface.json
npx dspack-gen run --dspack ./your-system.dspack.json --intent your-intent \
--prompt "..." --model ollama:your-model
context prints the compiled { system, schema, fewshot }; lint runs the
S1โS3 gates (exit code 2 = a governance rule fired โ your Phase 2 work,
executing); run is the full generate โ lint โ repair โ emit pipeline.
To render governed surfaces through dspack-emit (A2UI or json-render), your contract needs a mapping profile โ pure data describing how your components correspond to the target protocol's. That is a design-decision-bearing artifact of its own, with its own guide: Writing an emit profile. You author it in your own codebase against the published package โ the studio's Astryx profile is the reference for exactly that pattern. To see the whole chain assembled, dspack-studio runs it end to end (hosted replay).
Contributing a contract to the ecosystem (optional)
Everything above serves your use of dspack and touches no ecosystem
repository. Contributing a contract as a canonical example โ maintained
in this repo, byte-synced to consumers, exercised by the eval harness โ is a
different, heavier path: the contract lands in examples/ here first
(upstream-first), consumers re-sync their byte copies, dspack-gen grows
pinned fixtures and golden context/violating files, and dspack-emit needs an
in-repo profile with regenerated goldens. Open a discussion first if you
want that; it is maintainer-coordinated and not required for anything in
this guide.
Where this guide ends
This covers the upstream contract lifecycle: code โ snapshot โ governed, validated contract โ served/generated/linted. Emit-profile authoring lives in dspack-emit's guide. Keeping a contract continuously reconciled with design tools is deliberately out of scope (see aesthetic-function, currently paused). Questions the guide doesn't answer are adoption feedback the project wants: open an issue or discussion in this repository.