# Component contract template

A contract is what an agent can validate against. Prose is what it has to interpret. This template splits the two. From tokenstoagents.ai/kit.

## The sorting rule

Does it validate, or does it advise? Props, variants, allowed values, applied tokens: contract, JSON. Reasoning, judgment, when-to-use: guidance, Markdown. If a sentence contains "always" or "never," it's trying to be a contract; move it. If it contains "usually" or "prefer," it stays guidance.

## The contract (JSON)

Copy, rename, fill. One file per component.

    {
      "component": "Button",
      "version": "1.0.0",
      "description": "Triggers an action. Not for navigation; use Link.",
      "props": {
        "variant": {
          "type": "enum",
          "values": ["primary", "secondary", "destructive"],
          "default": "primary",
          "rules": {
            "destructive": "Only for irreversible actions. Requires confirm pattern for data loss."
          }
        },
        "size": {
          "type": "enum",
          "values": ["small", "medium"],
          "default": "medium"
        },
        "disabled": { "type": "boolean", "default": false },
        "label": { "type": "string", "required": true, "maxLength": 32 }
      },
      "slots": {
        "icon": { "optional": true, "position": ["leading"], "excludes": [] }
      },
      "tokens": {
        "background": "button.primary.background",
        "background-hover": "button.primary.background-hover",
        "label-color": "button.primary.label",
        "padding": "button.padding",
        "radius": "button.radius",
        "label-size": "button.label-size",
        "label-weight": "button.label-weight"
      },
      "states": ["default", "hover", "focus-visible", "disabled"],
      "a11y": {
        "role": "button",
        "focusVisible": true,
        "minTarget": "44px",
        "contrast": "AA"
      },
      "forbidden": [
        "Custom colors outside the tokens map",
        "variant=destructive for non-destructive actions",
        "label used as the only affordance for icon-only buttons (needs aria-label)"
      ]
    }

Notes on the shape: values are closed enums wherever possible, because closed lists are checkable and open strings are guesses. The tokens map names component tokens, which resolve through the semantic tier; an agent that follows the chain lands on your decisions. The forbidden list exists because agents (and humans) benefit from explicit negative space; the most common violations are worth naming, not implying.

## The guidance (Markdown, beside the contract)

Keep it lean; front matter and short sections, no bloat.

    # Button: guidance

    Use a Button when the user commits an action. Use a Link when they go somewhere.

    Prefer one primary button per view. When two actions compete, demote one to secondary.

    Destructive buttons pair with a confirm step when the action loses data. The confirm
    step is the safety mechanism; the red is just the signal.

    Icon-plus-label beats icon-only. Reach for icon-only when space forces it, never first.

## Rollout order

Write contracts for your top three components by usage (usually Button, Input, and whatever your product lives in: Table, Card, Modal). Serve them where your agents look: an MCP server if you run one, or the repo path your runtime files point to. Then run the cheap benchmark: twenty representative prompts against old docs vs contracts, count what breaks. Your own numbers beat anyone's headline.
