design

v0.3.0

Give it an RFC. It runs the Spec Kit workflow and implements the change using your design system.

Community extension — Independently maintained. Use at your own discretion. Learn more

Design System Extension for Spec Kit

Spec Kit Version License

You write the RFC. Your design system picks the components.
One command runs the whole Spec Kit workflow, and every UI decision in it
is answered by your design system instead of guessed by the agent.


What it does

You hand it an RFC:

/speckit.design.run docs/rfcs/newsletter-footer.md

That is the whole interface. You do not drive the phases, manage context or configure the workflow.

From that one line it asks what the RFC leaves open, writes a spec naming your real components and tokens, plans, implements, and then checks the result. Every answer about components, patterns, tokens and rules comes from your design system, through a short adapter. This repo holds no component knowledge of its own. It asks yours.

What that changes in practice:

  • It looks before it builds. Every UI need climbs a ladder: Recall → Reuse → Compose → Extend → Create. It is strict about what goes in and light on how things are used: a component that already covers the need is one search away, and the full argument, with a gap record, is reserved for what would be new. Something new is built as a lab component in your project, never quietly added to the design system.
  • It remembers. Each decision lands in a committed ledger, keyed by UI capability. The next feature that needs a date range reads the answer instead of searching again, even when it words the need differently.
  • It blocks. /speckit.plan does not start until every UI surface has a documented resolution, and your design system's rules land in the spec as numbered requirements (DS-001: ... MUST ...) with acceptance criteria. If the design system cannot be reached, planning stops. It never continues as if your system had nothing to say.
  • It keeps the context lean, and the calls few. Each phase starts with only what it needs, never the whole inventory. Everything else stays one call away, and each answer is remembered for the feature, so your design system's CLI is asked a question once, not once per phase and task. ds.sh cache stats says how often it was actually called.
  • It checks its own work. A script settles what needs no judgement (raw values, token names that do not exist, tokens the contract asks for that the code never uses), then a separate pass reviews the implementation against the spec and the ladder decisions once. Findings go back into implementation, and a second round checks only the fixes.
  • It fits any design system. A short YAML adapter maps a small capability contract onto whatever your system exposes: a CLI, files, or an MCP tool.

Spec Kit's hooks give you the when. This is the what, already built and tested.

Quick start

You need: Spec Kit >=1.0.0,<2.0.0, bash, Python >=3.9 with PyYAML (pip install pyyaml), and a design system an agent can read: a CLI, a registry, or a generated JSON file. If it only exists as a Figma library and tribal knowledge, there is nothing to ask.

1. Install it into your Spec Kit project. The extension, then the preset that ships inside it:

specify extension add design --from https://github.com/artursopelnik/spec-kit-design-system/archive/refs/tags/v0.3.0.zip
specify preset add --dev .specify/extensions/design/preset

Once the extension is listed in the community catalog, specify extension search design finds it too. To work from a clone instead:

git clone https://github.com/artursopelnik/spec-kit-design-system
specify extension add --dev /path/to/spec-kit-design-system
specify preset add --dev /path/to/spec-kit-design-system/preset

2. Run it on an RFC. The adapter is detected, so there is nothing to configure:

/speckit.design.run docs/rfcs/newsletter-footer.md

That's it. ✅

[!NOTE] The preset installs separately because extensions can only replace templates, which would fork your spec-template. Presets can append, so the design sections compose in without forking anything.

Want to see it first?

From a clone of this repository:

./examples/setup-demo.sh /tmp/design-demo
cd /tmp/design-demo

That builds a throwaway project wired to a fixture design system: ten components, two patterns, tokens, breakpoints, principles. Walk it with examples/README.md.

Or point the demo at one of the real systems the benchmarks use, with the RFC and the code a benchmark case is about:

./examples/setup-demo.sh /tmp/shadcn-demo --system shadcn --case date-range-filter
./examples/setup-demo.sh /tmp/radix-demo  --system radix  --case destructive-confirm
./examples/setup-demo.sh /tmp/mui-demo    --system mui    --case toolbar-mobile

The RFC you write

An RFC says what should change and why, before anyone builds it. It is the only input this extension takes. It is not a spec: no component names and no implementation. Which components to use comes out of the workflow.

What it has to look like may already be decided, and then it belongs in the RFC: paste your guidelines under a design heading, as free text, naming tokens where you know them. The workflow checks every token name against your design system, carries the brief into the spec's requirements, and validation checks the code uses them.

# RFC: Newsletter signup in the footer

## Problem
Visitors who like the blog have no way to hear about new posts.

## Proposal
A place in the footer to enter an email address and subscribe. After
subscribing, the visitor sees a confirmation.

## Out of scope
Managing or cancelling subscriptions.

## Acceptance criteria
- [ ] A valid email can be submitted
- [ ] An invalid email shows an error
- [ ] The visitor sees a confirmation afterwards

## Design guidelines
Dark footer band: background `color.surface.inverse`, text `color.text.inverse`.
Field and button sit on one row from `md` up, `space.4` apart.

## Open questions
- Double opt-in by email?

Start from templates/rfc-template.md.

Where the RFC lives does not matter: a markdown file, a GitHub or GitLab issue, a Jira ticket, text an MCP server handed you. The extension takes the text and ignores where it came from. None of those integrations live here; compose an extension such as spec-kit-jira instead.

And you never declare a type. The RFC above is a UI feature, and the extension works that out from the text:

KindExampleWhat happens
UI feature"Newsletter signup in the footer"Full workflow, your design system is consulted at every phase
UI change"Make the toolbar usable on mobile"Same, held to rules such as reflow and touch targets
UI bug"The error message on the login form is unreadable"Same, checked against your tokens and states instead of a one-off fix

What happens after you hit enter

RFC
 ↓
Clarify      what the RFC does not say
 ↓
Specify      the spec, from the RFC
 ↓
Plan         Recall → Reuse → Compose → Extend → Create, against the real system,
             then the spec's design requirements, with its real components and tokens
 ↓
Tasks        the plan broken into steps
 ↓
Implement    against the components' real props, states and tokens
 ↓
Validate     a mechanical scan, then an independent review, not the implementer
             signing off its own work
 ↓
Fix          findings feed back in; the next round checks only the fixes
 ↓
Verify       in the clean round: RFC criteria and spec requirements checked over
             the whole change
 ↓
Done

You only need the first command below. The rest are what it drives, and they also fire as Spec Kit hooks, so they hold for anyone working phase by phase.

CommandHookPurpose
/speckit.design.run <rfc>The whole workflow. The one to remember.
/speckit.design.checkbefore_planResolves principles and tokens, walks the reuse ladder, writes the design requirements into the spec, and gates planning on it (blocking)
/speckit.design.validateafter_implementThe independent checker

The ladder

The central rule: Recall → Reuse → Compose → Extend → Create. Before a new component is proposed or built, each rung is tried in order.

It is strict about what goes in and light on how things are used. Most surfaces stop on the short path: an earlier decision is recalled, or one search finds a component that covers the surface, and that is the whole walk. The full walk, with its candidate tables and a gap record, is reserved for the surfaces that would bring something new into your codebase.

RungQuestion
0. RecallHas another feature already decided this?
1. ReuseDoes an existing component cover it?
2. Compose (pattern)Does an existing pattern cover the arrangement?
3. Compose (components)Can existing components be combined?
4. ExtendCan a component be extended through a sanctioned mechanism?
5. CreateOnly when 1 through 4 are documented as insufficient.

It is DRY for UI: share and adapt what exists, build new as a last resort. Three rules keep it honest.

A rung is never rejected on a hunch. Rejecting one means naming the candidates searched and why each is insufficient. "Doesn't fit" is not a reason.

Describe capabilities, not components. Write "a control for picking a start and end date", never "a DateRangePicker". Naming the component pre-decides the ladder.

Create is fine, as long as it is visible. What gets built is a lab component: it lives in your project, is made from the system's own tokens and primitives, covers what this feature needs and no more, and is marked as not part of the design system. It comes with a gap record, the argued case for adding it there:

# Gap: selection of a start and end date

**Feature**: 003-booking-filters · **Design system**: acme-ds · **Version**: 1.4.2

## What is needed
A control for choosing a start and an end date together, where the two are
validated against each other.

## What was searched
| Candidate | Rung | Why it is insufficient |
|---|---|---|
| DatePicker | reuse | Single date only; no range semantics |
| Calendar | reuse | Display-only; no input affordance |
| Select | reuse | Wrong interaction model; enumerable options only |
| Calendar + Popover + two DatePickers | compose | Range validation has to live above both fields, which the composition cannot express without reaching into DatePicker internals |

## What we are building instead
A lab DateRangeField in `src/components/`, built from the system's tokens and
its Popover primitive, and marked as not part of the design system.

## What the design system could do
Give DatePicker a range mode, or ship the paired control as a pattern.

Note the title. The gap is named by the capability, not by the component that will close it. Calling it DateRangePicker would pre-decide the very question the record exists to argue. That is what separates a real gap your design system should close from a search that was not thorough enough.

A genuine gap is sent to your design system's intake when its CLI offers one. Otherwise it stays documented in the feature. Whether the lab component ever joins the design system is for its owners to decide, in their own process. Either way the outcome goes into the ledger, keyed by capability, and that is what Recall reads next time.

Your design system

The extension asks yours through a thin adapter that maps capabilities (search, component, tokens, principles, ...) onto a CLI call, a file read, or an MCP tool. One adapter can mix all three.

AdapterFor
shadcnshadcn/ui, via its CLI and registries. It publishes no machine-readable principles, so the default set applies unless you set principles.source
muiMUI (Material UI), via an inventory file
antdAnt Design, via an inventory file
chakraChakra UI, via an inventory file
radixRadix UI, via an inventory file
ark-uiArk UI, via an inventory file
static-jsonAny system with no CLI: point it at a generated inventory file
markdown-specsA design system written down as a folder of Markdown spec files (foundations/, tokens/, atoms/, molecules/, organisms/), read as it is
exampleTemplate to copy for your own CLI

The library adapters read an inventory file your project generates (default .design-system/inventory.json, shape in adapters/static-json.yml), because those libraries have no CLI to ask. Without the file the gate stops rather than guess.

If your design system is already written down as one Markdown file per foundation, token group and component, as the guides on making a design system AI-ready recommend, there is nothing to generate: markdown-specs reads the folder (default .design-system/specs, picked by auto when it exists). Each file's title, first paragraph, Usage and Don'ts sections and front matter become the component record, and the token tables under tokens/ (a name and its value per row) become the closed set of names the agent chooses from. Layout and tier names: adapters/markdown-specs.yml.

Adapters only map. They never hold rules or component knowledge, otherwise your design system would stop being the source of truth. Writing one: docs/adapters.md.

Principles

Principles are the rules the work is held to. They resolve from exactly one source, never merged:

1. your design system's CLI      ──┐
2. static data it ships          ──┼── first one that answers wins
3. a small default set           ──┘

The default set is fourteen broadly applicable principles: semantic markup, keyboard operation, focus, touch targets, reflow, reduced motion, state coverage, token use. It carries no colors, breakpoints or sizes, because those belong to your system.

If your system publishes principles without a CLI, name the file:

principles:
  source: "node_modules/@acme/design-system/principles.yml"

See principles/example.yml for the shape.

Definition of Done

Principles are your design system's rules. A Definition of Done is your team's, so the DoD is never asked of the design system, and nothing ships as a default. If you keep one, write it in .specify/extensions/design/definition-of-done.md as a plain list:

# Definition of Done

Agreed in the design system guild, revisit each quarter.

- Unit tests for every new component
- A Storybook story per variant
- Changelog entry
- Design review signed off by someone who did not build it

No schema, no ids, no verify field. Headings and prose around the list are ignored; the bullets are the DoD. Validation checks each item against what was actually built and raises unmet ones as ordinary DS-F-nnn findings, so they go through the same fix loop as everything else, and the feature is not done until they are resolved or argued in writing.

That list is an example to copy and edit, not a starting set. A DoD that arrived with an extension would raise findings against rules nobody at your place agreed to, which is why the file is empty until you write it. No file means no DoD: nothing is checked, nothing nags. To keep it elsewhere, a shared file in a monorepo or one your design system package ships, point dod.source at it.

Configuration

For most projects the whole file (.specify/extensions/design/design-config.yml) is one line:

adapter: shadcn   # or auto (default), mui, antd, chakra, radix, ark-ui, static-json, markdown-specs, your own

Everything else is optional and documented in config-template.yml: a bin override, a cwd for monorepos, a principles source, a dod.source, per-capability overrides, workflow.max_validation_rounds, cache settings, and gate.enforce: false while adopting. SPECKIT_DESIGN_* environment variables and a gitignored design-config.local.yml override the committed config.

Keeping it current

A design system ships; what the project wrote against the old one does not update itself. Two commands keep that visible, and both run in CI:

ds=.specify/extensions/design/scripts/bash/ds.sh
$ds sync record            # once: snapshot what the design system offers, and commit it
$ds sync --strict          # later: what was removed, deprecated or changed since,
                           # and which specs, ledger decisions and code still name it
$ds scan --strict --path "src/**/*.tsx"   # hardcoded values where a token exists

sync compares the design system's components and tokens with the committed snapshot (.specify/memory/design-system-snapshot.json) and lists every line in specs/, the implementation and the decision ledger that names something that moved. After the references are dealt with, sync record takes the new snapshot. scan reports literal colours, lengths, font stacks, durations, z-indices, opacities and font weights, and names the token that already carries the value where there is one. Without --strict both only report; with it they exit 1, so a pipeline can fail on them. A design system that cannot be asked fails the check rather than passing it.

The version prior decisions are checked against is read from the installed design system package (or design_system_package, or design_system_version in config), so staleness no longer depends on someone remembering to bump a number.

Does it actually help?

benchmarks/ is there to answer that with numbers: the same RFC run by the agent alone, with Spec Kit, and with this extension, against trimmed snapshots of shadcn/ui, Radix UI and MUI. Every arm is handed the design system in the same place, and every measure is computable from any arm's output, so none of them rewards the extension for merely having run.

The scores also read as pass or fail, so a result can be stated plainly, like "used the component the system already had in 9 of 10 runs, against 4 of 10 without it", beside what each arm cost in tokens and money. Scoring higher at three times the cost is a trade, not a win.

No results are published yet. The suite ships the harness, the cases and the scorer. The numbers need an agent, many runs and a stated model. benchmarks/README.md says what to publish alongside one.

How it is built

Three layers, only the first is public:

  • Commands (commands/): agent-facing prose describing what to do, in what order, and what not to accept.
  • One script (scripts/python/design.py, with a bash shim): prerequisites, capability dispatch, principles, focused context, workflow position, RFC parsing, the ledger, the implementation scan and the design system sync. Always emits JSON.
  • Adapters (adapters/): declarative YAML, no code.

There is no run-state file. Workflow position is read from the artifacts the work already produces (spec, plan, tasks, design document), so an interrupted run resumes by reading.

More: architecture · autonomous workflow · adapters · troubleshooting

Troubleshooting

The usual first stops:

  • Planning stops at the gate. The design system could not be reached, or a UI surface has no documented resolution. Run .specify/extensions/design/scripts/bash/ds.sh gate --json and read REACHABLE and PRINCIPLES_SOURCE.
  • PyYAML is required. Install it into the interpreter the shim finds: python3 -m pip install pyyaml.
  • The design system changed mid-feature, and answers look stale. Answers are remembered per feature for cache.ttl_minutes. Run ds.sh cache clear to ask again; ds.sh cache stats shows how often the CLI was actually called.
  • bash\r: No such file or directory. The checkout was converted to CRLF. Re-clone, or install from the release archive.

More in docs/troubleshooting.md.

Contributing

Issues and pull requests are welcome. Before opening one, run the tests below; CI runs the same, plus a real install into a Spec Kit project. An adapter for another design system is the most useful contribution, and docs/adapters.md shows how to write one. Adapters only map, so a pull request that teaches an adapter a rule or a component will be asked to move that knowledge into the design system instead.

Releases follow docs/publishing.md, and every change is recorded in CHANGELOG.md.

Support

Questions and bug reports go to the issue tracker. Include the output of ds.sh gate --json and your Spec Kit version (specify --version).

Development

pip install pytest pyyaml
python -m pytest

CI also installs the extension into a real Spec Kit project, validates the manifests, checks that hooks register, and walks the example end to end.

commands/      agent-facing command bodies
scripts/       ds.sh shim + design.py
adapters/      capability mappings
principles/    default fallback set and an example
preset/        spec, plan and constitution addenda
templates/     RFC template
examples/      fixture design system and demo project
benchmarks/    cases, design system snapshots, scorer and report
docs/          architecture, adapters, workflow, troubleshooting, publishing
tests/         pytest suite, one file per concern

License

MIT. See LICENSE.

Stats

3 stars

Version

0.3.0release
Updated 7 days ago

Install

Using the Specify CLI

specify extension add design --from https://github.com/artursopelnik/spec-kit-design-system/archive/refs/tags/v0.1.0.zip

License

MIT