---
name: specifying-component-contracts
description: Defines the per-component metadata an agent needs to use a component correctly — props with defaults, exhaustive variant enums, verbatim imports, composition rules, accessibility preconditions and deprecation pointers. Use when authoring a new component, documenting an existing one, designing a component API, building a component manifest or registry, or when the user asks what agents need to know about a component. Applies when prop hallucination or invented variants are the problem being solved.
license: CC0-1.0
---

# Specifying component contracts

Four organisations have independently formalised this — Atlassian's TypeScript
content schemas, Storybook's `components.json` manifest, Meta's CLI-as-contract
in Astryx, and Figma Code Connect — and they converge on close to the same
field set. Nobody has published a cross-vendor spec, so this is that field set
written down.

## The ten fields

Ranked by how much drift each one prevents.

1. **Props: name, type, default, required.** Defaults matter more than they
   look: an agent that cannot see the default will pass one explicitly, and it
   will pass the wrong one.
2. **Exhaustive variant enums.** Not `size?: string`. `size?: 'sm' | 'md' | 'lg'`.
   An open prop type is the single largest source of drift in agent-written
   code, because every invented value that survives review becomes a sample
   the next generation copies.
3. **The import statement, verbatim.** This kills the whole class of failure
   where documentation names an export the package does not have.
4. **Composition rules.** Valid parents and children. Agents nest wrongly by
   default.
5. **Existence allow-list.** The set of components that exist, so anything
   else is provably invented rather than merely discouraged.
6. **Do and don't, with the anti-pattern named.** "There is no Box component"
   beats "follow our layout conventions".
7. **Accessibility preconditions.** Stated as requirements, not suggestions —
   see the semantic accessibility skill for why linters will not catch these.
8. **Deprecation with a replacement pointer.** `deprecated: true`,
   `useInstead`, `since`. Agents reach for deprecated APIs at a structural
   rate because old code outnumbers new code in nearly every repository. That
   makes it a retrieval problem, not a prompting problem.
9. **Version pinning.** Which API surface this describes. Ant Design ships
   per-minor snapshots; almost nobody else does.
10. **A rendered usage exemplar.** Real composed usage with args applied, not
    just a type signature. Storybook generates these from the final rendered
    story.

Nothing in production carries all ten. Version pinning and machine-readable
deprecation are the two most consistently missing.

## Shape it as data, not prose

Write the contract once, in a typed source beside the component, and generate
every surface from it: the MCP tool payload, the docs page, the agent
instruction file. Hand-written agent docs rot; generated ones cannot.

```ts
export const buttonContract = {
  name: "Button",
  import: "import { Button } from '@scope/design-system'",
  props: {
    variant: { type: ["primary", "secondary", "ghost"], default: "secondary", required: false },
    size: { type: ["sm", "md", "lg"], default: "md", required: false },
    children: { type: "ReactNode", required: true },
  },
  composition: { validParents: ["*"], validChildren: ["text", "Icon"] },
  a11y: ["Icon-only buttons require an accessible name that states the action."],
  deprecated: false,
} as const
```

## The check that pays for itself

Assert in CI that every symbol named in your agent-facing docs is actually
exported by the package. Meta's Astryx documented a component under a name its
package does not export; the agents read the README, used the name, and
produced a build failure. Nobody hallucinated anything — the documentation was
the defect.

## Verification

For each component you contract:

1. Every prop lists a type and, if optional, a default.
2. No prop is typed as a bare `string` where a union would do.
3. The import string compiles when pasted verbatim.
4. The a11y preconditions are things a human would have to read to check.
