Default AI output is generic Tailwind soup that matches nothing you own. This module makes generators land on your system — instruction files and exemplars condition them, registry and Storybook MCP servers ground them in real component APIs, and schema-first patterns make forms, state, and data layers verifiable instead of plausible.
Arjun owns the design system at a mid-size e-commerce company: 40 components, tokens, Storybook, three product teams consuming it.
A product engineer ships an AI-generated checkout widget. It contains a hand-rolled <CustomButton> — 80 lines duplicating Arjun's Button, with a variant="subtle" prop his Button never had and raw #6b7280 grays where tokens exist.
It's the fourth such PR this month. Each one works. Each one adds a parallel, slightly-wrong component universe the team will maintain forever.
Nobody told the generators the design system exists. The models did the statistically likely thing: emit the shadcn-flavored output their training data is saturated with.
Arjun stops reviewing symptoms and starts engineering the context: instruction files that name the primitives, exemplar components in the prompt, and a registry MCP server so agents query real component APIs instead of inventing look-alikes.
Generated PRs import from @acme/ui. The off-system lint he added catches the stragglers in CI, not in his review queue.
Arjun's month is the defining component-development problem of the AI era: the tools generate a design system fluently — just not yours. Left unconditioned, every assistant on the team converges on the same generic output, and your design system dies by a thousand plausible duplicates. The fix is not better reviewing. It is conditioning the generator before it types: telling it what exists, showing it what good looks like, and — at the advanced tier — wiring it to query the truth instead of trusting it to remember. This module covers that discipline end to end, then applies the same grounding mindset to the rest of the component workload: forms, Storybook, state refactors, data layers, and typed API clients.
Every serious assistant now reads a persistent instruction file from the repo — Copilot's .github/copilot-instructions.md, Cursor rules, Claude Code's CLAUDE.md. For frontend teams this file is the single highest-leverage artifact you can write, because it runs on every generation by every teammate. The generic version ("use TypeScript, follow our conventions") does nothing. The version that works is concrete and mechanical:
@acme/ui. Never create a component that duplicates an existing primitive. Before writing any button, input, card, dialog, or badge, use the existing one."var(--color-*) / Tailwind theme tokens. No raw hex, no arbitrary values like p-[13px]. Spacing uses the 4px scale tokens."button, a, label). Every input has an associated label. Focus states are never removed." (M5 deepens this.)src/components/DataCard/ — it is the reference implementation."The exemplar line is the underrated half. Models are few-shot learners: one actual component from your codebase in context — with its prop patterns, token usage, test file, and story — moves output quality more than a page of prose rules. Keep two or three "gold" components deliberately polished and small, and reference them by path so agentic tools open and imitate them.
Write the instruction file with an enforcement partner: an ESLint config that bans raw hex values, arbitrary Tailwind values, and imports that bypass @acme/ui. The instruction file steers generation; the lint catches what steering missed. Neither alone survives a deadline — together they hold in CI.
Instruction files reduce hallucination; they cannot eliminate it, because the model is still generating your component APIs from memory it doesn't have. The structural fix is MCP grounding: give the agent a tool that returns the truth.
A shadcn-style registry MCP server (or an internal registry server, or the Storybook MCP addon) lets the agent do what you would do — look up the component, read its actual props and variants, and install or import the real thing. The workflow difference is stark, and Lab 03 has you measure it directly by requesting the same data-table screen with and without the registry wired:
Asked for a data table, the agent emits a plausible <DataTable columns={...} striped> — a component that doesn't exist, with props it invented, styled with its training-data defaults. Everything compiles in the agent's imagination and nothing in your repo. You discover the fiction at typecheck — or worse, when it hand-rolls the whole table to make its fiction true.
The agent calls the registry, gets the real Table API — the actual prop names, the actual variants, the install command — and composes your components with your conventions. Output references reality because it fetched reality. Review shifts from "does any of this exist?" to "is this composition right?" — a senior-level question worth your time.
The same grounding logic extends to Storybook: the Storybook MCP addon exposes your stories — component inventory, states, controls — as queryable context, which both grounds generation and gives agents a render target for verification (below).
Grounding is only as good as the registry. If your design system's props are undocumented or your Storybook is stale, the agent faithfully fetches garbage. Registry-grounded generation quietly forces the platform-engineering hygiene you always meant to do — that is a feature, not a cost.
Forms are the highest-frequency frontend task and the place where generated happy paths hurt most, because the unhappy paths carry the business logic: validation, server rejection, double-submit, dirty-state warnings. The pattern that works is schema-first with state enumeration: define the Zod schema yourself (it is the contract — human work), then have AI generate the react-hook-form wiring with the full state matrix demanded explicitly:
Given this Zod schema [paste], generate the form component using
react-hook-form + our FormField primitives (see src/components/Form/).
Enumerate and render ALL of: per-field validation errors, form-level
server error, dirty-unsaved warning, disabled-while-submitting,
submit success, and empty/initial. No state may be represented
only in comments.
Then hand-verify the unhappy paths in the browser — submit with the network failing, double-click submit, navigate away dirty. The model enumerates flawlessly when told to; it is the telling that is the skill, and the manual unhappy-path pass that is the review.
Text-only generation is open-loop: the model emits JSX and never sees a pixel. Storybook closes the loop cheaply. The workflow: have the agent generate (or update) stories for every variant and state alongside the component; run Storybook; the agent (via the MCP addon or a screenshot tool) — and you, in two seconds of scanning — verify each state actually renders as intended. Stories become three things at once: the generation contract ("these states must exist"), the visual review surface, and the input to visual-regression gates later (M5). A component PR without stories for its states is, under this discipline, incomplete by definition — for humans and agents alike.
The second half of component work is not greenfield — it is evolving the client architecture you already have: prop-drilling that needs scoped stores, useEffect-fetch spaghetti that needs TanStack Query. AI is genuinely strong at the mechanical conversion and genuinely dangerous at the semantics, so the discipline is sequence:
| Task | Delegate to AI | Keep human |
|---|---|---|
| Data-flow inventory | Full read-and-map pass | Spot-check accuracy |
| Characterization tests | First draft generation | Assertion-quality review (mutation-check, M5) |
| Store/query conversion | Mechanical slice-by-slice rewrite | Slice ordering, rollback decision |
| Cache semantics | Propose defaults | Decide staleness, invalidation, optimistic policy |
| Typed API client + MSW mocks | Full generation from OpenAPI/GraphQL schema | Drift check in CI, edge-fixture realism |
The last row is its own quick win: openapi-typescript or GraphQL codegen plus AI glue turns a schema into typed fetch hooks and MSW handlers in one pass. Put a drift check in CI (regenerate and diff against the committed client) so the contract cannot silently rot — the AI-era version of "the mock lied."
The agent's characterization tests and the agent's refactor share the same blind spots — a misread of current behavior gets faithfully preserved in both. Before trusting the pin, break the component deliberately (invert one condition) and confirm the tests actually fail. A pin that can't fail protects nothing.
CLAUDE.md / Copilot instructions and generate one small component to compare before/after.This module's playbooks: pb-02 (Instruction Files That Keep Generated UI on Your Design System), pb-03 (Registry-Grounded Generation), pb-04 (Schema-First Forms), pb-05 (Complex Interactions: AI Scaffolds, You Own the Last 30%), pb-06 (Storybook as the Agent's Visual Verification Loop), pb-07 (Typed API Clients and MSW Mocks from the Schema), and pb-08 (State and Data-Layer Refactors with Characterization Protection). Lab 01 grades your instruction files with an automated on-system lint; Lab 03 is the registry A/B with a comparison memo.
Generators emit the design system they were trained on unless you make yours louder: instruction files state the rules, exemplars show the shape, registry MCP serves the truth, and an off-system lint holds the line in CI. Condition first, generate second, and review only what conditioning can't decide.