spec.md — one file, three audiences
May 25, 2026
Most component libraries pick a documentation model and stick with it:
- Markdown in the docs site. Designers love it; AI tools can’t query it.
- Storybook stories. Engineers love them; designers don’t open Storybook.
- TypeScript signatures. AI tools can parse them; designers see nothing.
We tried all three. None of them were a contract; they were descriptions of one. They went out of date the moment the component shipped.
helixui picked a fourth way: every component owns a sibling spec.md
that is the contract.
What it looks like
packages/core/src/components/button/spec.md:
---title: Buttonstatus: stablesince: 0.1.0package: "@helixui/core"import: "import { Button } from '@helixui/core'"description: Triggers an action when clicked.category: formtags: [clickable, action, primary, destructive, submit, cta]props: - name: variant type: "'solid' | 'soft' | 'ghost' | 'outline'" default: solid - name: tone type: "'brand' | 'neutral' | 'danger'" default: brand - name: size type: "'sm' | 'md' | 'lg'" default: md - name: disabled type: boolean default: falsetokens: - color.bg.action.brand.default - color.bg.action.brand.hover - radius.md - spacing.3a11y: - inherits native <button> semantics - icon-only buttons require aria-label - focus ring via :focus-visible---
## Anatomy
A native `<button>` with no wrapper. Spread props go to the button.
## Examples
```tsx<Button>Click me</Button><Button variant="outline" tone="neutral">Cancel</Button><Button tone="danger">Delete</Button>The frontmatter is **machine-readable**. The body is **human-readable**.Same file. Same git history. Same review.
## Three audiences
### 1. The docs site
`apps/docs/` ingests every `spec.md` at build time and turns it intoa Starlight content page. Props tables are auto-rendered from thefrontmatter. Examples render with full syntax highlighting. When aprop is added, the docs site picks it up on the next deploy.
No "remember to update the docs" step. No drift.
### 2. The LLM manifest
`pnpm build:llms` walks every `spec.md` and emits`llms.txt` (compact tree) and `llms-full.txt` (full prose, ~80 KB).These are static files served from the site root. Drop them into anyLLM context window — the model has the system in memory.
You can read the live manifests:
- <https://helixui.ai/llms.txt>- <https://helixui.ai/llms-full.txt>
### 3. The MCP server
`@helixui/mcp` serves the same `spec.md` files over JSON-RPC stdio.Agents call `helixui.search "button danger"` and get back the relevantspec frontmatter without parsing markdown. They call `helixui.validate"<Button variant='outline'>"` and the server cross-references thespec to confirm `outline` is a valid variant.
The MCP server doesn't have its own database. It reads from the samefiles everything else reads from. There's nothing to keep in syncbecause there's only one source.
## Why this is a contract, not a description
Two things lift `spec.md` from "doc style" to "contract":
1. **CI fails if a component lacks a spec.** `pnpm build:llms` errors out when a `packages/core/src/components/<name>/` directory exists without a `spec.md`. Forgetting one isn't an option.2. **The spec ships in the npm tarball.** `@helixui/core`'s `package.json` `exports` field includes `"./components/*/spec.md": "./dist/components/*/spec.md"`. A consumer who never opens our docs site still has the contract in their `node_modules/`.
## The cost
`spec.md` is overhead. We pay it on every new component (about 30minutes per spec, with examples). We pay it again on every APIchange. The discipline is real.
What it buys:
- Docs that never drift.- AI tooling that doesn't hallucinate.- A schema we can validate against in tests.- A format Figma plugins can read (next quarter).
Three engineers can keep the spec catalog current. A larger teamwould have made the docs go stale by now. We measured.
## How to write a good spec
- **Be terse.** A spec isn't a tutorial. The body is for examples, not philosophy.- **List every prop.** If you ship it, document it. No "private" props in production.- **List every token.** Even ones the consumer is unlikely to override. Future theme work depends on this list being accurate.- **List a11y constraints as imperatives.** "Icon-only buttons require aria-label" not "consider an aria-label."
The [SPEC_FORMAT.md](https://github.com/021flow/helixui/blob/main/packages/core/src/components/SPEC_FORMAT.md)file in the repo has the full schema.
## What it doesn't replace
- **Code review.** A spec describes intent; the code is what runs.- **Type tests.** Frontmatter is plain YAML, not TypeScript.- **Long-form articles.** This blog post wouldn't fit in a spec.
Spec is for *contracts*. Everything else lives in the docs site, theshowcases, the blog.
## Try authoring one
Want to write a spec? Pick a component without one (the Banner is agood candidate) and follow `SPEC_FORMAT.md`. Open a PR. Look at howthe docs site, `llms-full.txt`, and the MCP server light up in the CIpreview — that's the contract paying off in real time.
---
*Open question: should `spec.md` carry runnable examples? We say nofor now (it'd make the format too heavy), but the Markdown studioshowcase is changing our minds.*