Home
Engineering

How We Made Our Design System Bilingual: One Source of Truth for Humans and AI Agents

A case study in turning Storybook into a shared interface for humans and AI agents – boosting developer flow state and component fidelity.
David Cai
UI Software Architect
August 17, 2026
-
14
min

AI coding agents are good at producing front-end code that looks right. Ask one to build a settings page and you will get a plausible settings page. However, look closer and the cracks show: hard-coded hex values where design tokens should be, and a hand-rolled button that almost matches the one in your component library. Each choice is defensible on its own. None of it belongs to your design system.

The knowledge to do it right already exists. Trinity, BILL’s design system, records the components and tokens, along with the rules for using them, and it serves designers and engineers well. But it was built for humans: people who open a documentation site and read. AI agents need the same knowledge in a shape they can query, in whatever environment they happen to run.

So we turned Storybook into a shared interface for humans and AI agents. Storybook remains the single source of truth. On top of it we built a CLI, a hosted MCP (Model Context Protocol) server, and a set of agent skills that make AI agents first-class consumers of the design system. In our A/B tests, grounding an agent this way cut token usage by 31–63% per task and held generation time steady at five minutes per run. Component fidelity improved as well: the grounded agent produced correct components on the first pass, where the ungrounded agent shipped wrong ones.

This post walks through the full case study: the problem we started with, the architecture, one task traced end to end, the lessons that shaped the design, and the results we measured.

The original problem

Before building this harness, we spent time observing how an ungrounded AI agent behaves when asked to generate UI code in our monorepo.

The ungrounded agent cannot ask the design system for answers, so it excavates them. It runs cat and grep over the built dist/ output of our component libraries, trying to reverse-engineer component APIs from compiled sources. That exploration is expensive (it can burn millions of tokens on a single task) and brittle, because compiled output was never meant to be read as documentation.

Some gaps cat and grep cannot close. Design docs call one of our components a “radio tile”, but for historical reasons its code name is “in-container-selector”. Nothing in the source connects the two names. In one test run we asked the agent for a “radio tile group”, but it shipped standard “radio buttons” instead. In another run it produced a form UI where no fields were rendered at all.

Even when the agent finds the right component, it improvises the styling: raw hex values like #1c1b1b instead of our color tokens, and max-width: 560px where a spacing token belongs. Each violation costs a review comment and a correction round. The output resembles our product without being built from it.

All of these failures share a cause: the agent was working blind. Our documentation site held the answers, and an agent with browsing tools could technically read the pages. But the site was never built with AI agents in mind, and consuming it page by page is slow and unreliable.

The architectural insight

Everything the agent needed was already written down. Component APIs, code examples, accessibility guidance, and design tokens all live in Storybook, and keeping them current is an embedded part of our development cycle. What was missing was a structured interface agents could query directly.

Our bet: instead of writing new docs for AI, turn the existing source of truth into a shared interface for humans and agents.

Storybook fits this job because stories are code. A story file imports the real component and renders it, so the documentation compiles against the same source it describes. Rename an input or remove a property, and the affected stories break the build; a component change that skips its docs fails CI before it merges.

Stories also live in the same repository as the components and change in the same merge requests. Documentation that ships with the code has no separate lifecycle to fall behind on.

We considered and rejected three other paths:

  • A parallel set of AI-facing docs (system prompts, per-repo rule files) would easily get stale as soon as the design system iterated, leaving two sources of truth with one of them quietly wrong.
  • Retrieval (RAG) over the rendered docs site answers questions but cannot teach workflows, and retrieval quality over pages shaped for reading is unpredictable.
  • Fine-tuning a model on the design system would lag every release and lock us to one vendor and model generation.

The design system would become bilingual by leveraging one body of knowledge with two delivery shapes. Humans get the browsable Storybook site. Agents get the same content as structured data, plus instructions for using it.

How the system works

One source of truth, two delivery paths, and shared workflow guidance for different kinds of consumers.

Storybook, and the pipeline that keeps it honest

The design system lives in an Nx monorepo where every component is its own project, and every project carries its own tech documents: a get-started guide, API reference, code examples, accessibility recommendations, a migration guide, etc. Building and updating these docs is part of shipping a component change. Agent skills generate and update these tech documents, which keeps the work cheap enough that nobody skips it.

The anti-drift mechanism is the build graph itself. When a component’s docs change, the Nx dependency graph marks the CLI project, which depends on every component’s docs, for rebuild. The CLI gathers the documents and is distributed as an npm package. The MCP server project depends on the CLI project, so it rebuilds too. Components, CLI, and MCP are versioned together and published in one automated CI run. There is no human in the loop to forget a step.

Every generated tech manual carries its own provenance, so an agent (or a human) can see what it was built from:

Tech Manual Version: Generated for @bill/trinity.ng@x.y.z on 2026-03-16

The CLI: docs, piped into the terminal

The Design System CLI (@bill/trinity.ng-docs) is the same knowledge rebuilt for machines: a thin layer that curates the Storybook docs and serves them to a terminal as structured output. One flag per invocation, JSON on stdout:

Usage: trinity.ng-docs <flag>

Flags (exactly one per invocation):
 --components       List all component names with their aliases.
 --icons            List all design-system SVG icon names.
 --tokens           List all Pathfinder tokens with values.
 --typography       List @use + @include mixins for typography.
 --font-faces       List the @font-face declarations.
 --component=<name> List absolute paths of a component's docs.
 --icon=<name>      List an icon's data and rendered SVG.

Modifiers:
 --theme=<name>     Theme for --tokens and --font-faces. Valid: bill, aire.
 --include-legacy   Include legacy tokens in --tokens output.

Ask for tokens and you get names and values, with no web page to parse:

[
 { "name": "--tri-color-fill-accent-secondary", "value": "#ff5a0a" },
 { "name": "--tri-color-fill-danger",           "value": "#ffdad4" },
 { "name": "--tri-space-300",                   "value": "1.5rem" },
 ...
]

Component docs work in two steps: --component=data-table returns the paths of that component’s documents (get-started, accessibility, migration), and the agent reads only the ones the task needs. The slicing keeps context small: the agent pulls a few focused documents instead of ingesting an entire library.

The MCP server: same answers, no shell required

Not every tool environment has a shell. Browser-based and hosted tools usually cannot run npx, so we built a hosted MCP server to expose that same curated knowledge directly to agents:

  • get_component_names - List all component names and aliases
  • get_component_details - A component’s manual: API plus code examples
  • get_design_system_tokens - Design tokens (colors, spacing, typography)
  • get_typography_mixins - SCSS text-style mixins
  • get_font_faces - Global font-face declarations
  • get_icon_names - Approved SVG icon names
  • get_icon - Fetch a specific icon
  • get_mcp_version - Server version (also used as a health probe)

Because the MCP server is built from the CLI package, the two transports always serve the same answers.

Skills: teaching agents how to work

Information alone does not make an agent reliable; our early runs taught us that. The Design System skills are instruction sets that load into the agent and turn the docs into behavior. Three parts do most of the work.

First, a data-source check that runs before any code is written. The skill probes the MCP server (get_mcp_version); if it is unreachable, it falls back to the CLI at a pinned version; if neither responds, it refuses to proceed:

TRINITY DATA SOURCE UNAVAILABLE
Neither Trinity Ng MCP nor the ng-docs CLI responded. Cannot verify components or tokens.
No frontend code will be generated until a data source is confirmed.

That refusal is deliberate: a stalled task is easy to recover from, while plausible ungrounded code that slips into a merge request is the failure this system exists to prevent.

Second, hard styling rules. The no-hardcoded-values rule is written as an anti-pattern list the agent checks itself against:

/* Never */
color: #473cc5;
padding: 16px;

/* Always */
color: var(--tri-color-fill-accent-secondary);
padding: var(--tri-space-300);

Third, an escalation path for gaps. When no design system component matches, the skill requires the agent to stop and report what it needed and what it searched, then get approval before writing custom code. A gap becomes a visible decision instead of a silent hallucination.

The skills also absorb the plumbing: they pick MCP or CLI based on what is available in the user’s environment, and users never have to track which transport is active.

Skills can drive whole workflows too. The /create-design-prototype skill scaffolds a prototype project with Claude Code: a Git repo, build tooling, and a CI pipeline that hosts the prototype on AWS CloudFront for colleagues and customers to review. With this skill, designers can now set up their own prototype projects without waiting on engineering support.

Humans keep their interface, too

None of this displaced the human-facing side. Designers and engineers still browse Storybook. The same docs also feed Glean, our company knowledge search, so a PM asking a design system question gets an answer from the same source the agents consume. There is no parallel wiki to maintain and no second copy to go stale.

For designers, the grounding layer works across various design tools, and we deliberately do not mandate a single tool.

Figma Make reuses the Design System skills for designers who want to stay in Figma.

Claude Design takes a looser approach: we generate a design.md brand-guidelines file from CLI output and let it build pseudo-components. That looseness suits one-off explorations that can step outside the current visual language.

Claude Code (desktop or CLI) is for designers who want full control of the agent and its harness; the desktop app’s built-in browser annotation tool lets designers comment directly on a running prototype and send the feedback straight back to the agent. Iterating on a design this way feels flexible and intuitive.

Prototyping is now fast enough that designers do not have to bet everything on a single direction. They can build several candidates, ship each as a running web app behind a shareable link, and gather user feedback. Engineers then implement the winner with confidence, because it already speaks the design system’s language.

Claude Design bootstrapped with a design system generated from Design System CLI output.
Design review in Claude Code Desktop: designers highlight the running page, comment, and send it straight back to the agent. They point at the thing instead of describing it.

How a change propagates

A change to a design system library updates its Storybook docs; the Nx graph rebuilds and republishes the CLI and MCP from those docs; the skills read whatever the transports serve. Update once, and every consumer, human or agent, inherits it in the same release.

One task, two tests, end to end

To test the effectiveness of our Design System harness, we ran a set of A/B tests: the same front-end tasks given to the same agent twice, once without the Design System harness and once with it. The full numbers are in the evaluation section below. Here is one of those prompts submitted to AI agents:

Create a user profile form at lib/workspace.component.html. This form should have Street (input), State (select), Country (search select), Date of Birth (date picker), Base salary (currency input), Phone number (phone input), Department (radio tile group).

Step 1: the skill loads and picks a transport. The data-source check probes the MCP server. It is up, so the run uses MCP. (If it were down, the CLI fallback would serve the same data at a pinned version.)

Step 2: component discovery. The agent calls get_component_names and maps each requirement in the prompt to a real component: input, select, search-select, date-picker, currency-input, phone-input. For “radio tile group”, the docs resolve the vocabulary problem directly. The manual opens:

Radio tile group (previously In Container Selector) — A radio tile component. Previously known as in-container-selector. Offers users a single selection from two or more options. Unlike radio buttons, tiles can be deselected by default.

That single line is the bridge between the designer’s word and the component’s name, and grepping compiled output has no way to surface it.

Step 3: the manual supplies usage. get_component_details("radio-tile-group") returns the versioned tech manual: properties tables, form-control integration, accessibility notes. The manual includes a reactive-forms example:

<form [formGroup]="addressForm">
 <tri-ng-in-container-selector-group formControlName="state">
   @for (s of states(); track s.value) {
     <tri-ng-in-container-selector
       [title]="s.label"
       [value]="s.value"
     ></tri-ng-in-container-selector>
   }
 </tri-ng-in-container-selector-group>
</form>

Step 4: grounded generation. The agent composes the form from real Design System components, binds the Department field the way the manual’s form-control example shows, and pulls spacing and color from get_design_system_tokens. In our test run, the grounded agent produced the correct radio tiles, with correct alignment and form wiring, on the first pass from that single prompt.

Without the harness, the same task went differently: the agent dug through dist/ output to guess APIs, missed the radio-tile mapping, and shipped standard radio buttons with hardcoded hex and pixel values. The grounded run also cost less, because reading three curated documents is cheaper than parsing a component library’s compiled source. The token usage and generation times are in the evaluation below.

The lessons that shaped the design

The pipeline in that walkthrough did not arrive fully formed. Three problems we hit along the way shaped the current design.

The stale-docs problem. Early on, an outdated get-started guide for our Data Table component caused agents to generate code against deprecated APIs. The docs said one thing, the library did another, and the agent had no way to tell. This is why doc generation is wired into the Nx build graph and why every manual carries a version header naming the library versions and generation date it was built from. Freshness became a build artifact instead of a discipline.

The transport problem. Our hosted MCP server is under active development and is not reliable around the clock yet. Early on, agents stalled mid-task when the server went down. The fix is the three-layer data-source check in the skill: probe MCP first, fall back to the CLI at a pinned version (the npm registry is more reliable than a young service), and refuse to generate frontend code if neither responds. We chose a hard stop over silent degradation: an agent that cannot verify components should not write component code.

The information-without-instructions problem. Shipping the CLI did not change agent behavior by itself. An agent with docs tooling available still digs into node_modules out of habit. Behavior changed when the skills made the workflow explicit and non-optional: run the data-source check first, consult get_component_names before assuming any API, never read compiled output, and escalate gaps instead of improvising. Agents need the workflow taught along with the information.

Evaluation and results

The walkthrough shows how grounding changes an agent’s behavior on a single task, but a single task is an anecdote. To judge whether the harness is worth adopting, we needed measurements. That is what the A/B tests provide: with the same tasks and prompts given to the same agent on both sides, any difference in cost or output comes down to the grounding.

Setup. Four tasks, each run in two configurations: A, the agent without the Design System harness (no skills, no MCP, free to explore the repo), and B, the same agent with the harness (skills loaded, MCP and CLI available). The tasks: one design-to-code scenario (implementing a screen from a Figma design, provided as a Figma URL via the Figma MCP), and three single-prompt scenarios, each a multi-field form spanning seven component types like the user-profile task above. Both configurations used Claude Sonnet 4.6 with medium thinking in Claude Code. Token counts are input tokens, summed from the Claude Code session transcripts with a parsing script; times are wall-clock from prompt to final output. One scored run per task per configuration; the limitations below address the small sample.

Tokens. The harness cut token usage on every task: 39% on the design-to-code scenario (8.2M to 5M) and 31–63% on the single-prompt scenarios (5.1M to 1.9M, 2.9M to 2M, 2.8M to 1.5M). Across all four tasks, usage fell from 19M to 10.4M, a 45% reduction.

Orange: without the harness. Blue: with it. The biggest single win (Single Prompt 1, 5.1M → 1.9M) comes mostly from eliminated source-code excavation, since curated docs replace grep over compiled output.

Time. Ungrounded runs took 5, 6, 4, and 9 minutes (median 5.5, range 4–9); the 9-minute outlier came from the agent struggling with source code query and parsing. Grounded runs landed at 5 minutes flat on all four tasks. Predictability eliminates friction, keeping engineering teams in their flow state.

Without the harness (orange), generation time swings 4–9 minutes across the four scenarios; with it (blue), every scenario lands at 5 minutes.

Quality. Token savings alone do not prove better output, so we compared the generated code itself:

  • Component fidelity. Without the harness: wrong components — standard radio buttons where “radio tiles” were asked for (round 1), and a form where no fields rendered (round 2). With it: correct Design System radio tiles, right alignment, first pass.
  • Design tokens. Without: colors and typography frequently hardcoded (#1c1b1b, 560px). With: colors, spacing, and typography follow design tokens.
  • Library discovery. Without: brittle cat and grep exploration of built dist/ output to infer APIs. With: guided context served directly by MCP or CLI.
  • Iteration efficiency. Without: more corrective back-and-forth to reach acceptable code. With: less back-and-forth, cleaner code sooner.
  • Time predictability. Without: variable, hard to plan around. With: consistent and predictable.

Limitations. This is a small evaluation: four tasks we chose ourselves, one scored run per task per configuration, a single model and harness version, and quality judged by our own engineers rather than blind reviewers or automated scoring. The numbers were strong enough to make the harness our default for front-end agent work, and they match what we see in daily use, but treat them as one team’s measurements. The obvious next step is a larger pass with repeated runs, medians and ranges, and automated checks for token violations, unresolvable imports, and accessibility.

Main takeaways

  1. Documentation is infrastructure now. Agents turn docs directly into code, so doc quality sets code quality. Treat freshness as a build problem: wire doc generation into your dependency graph and version docs with the code they describe.
  2. You probably already have the source of truth. If you have Storybook or anything like it, the knowledge agents need already exists in a shape meant for human browsing. The work is exposing it as structured, sliceable data rather than writing a second set of docs for AI.
  3. Ship one source over multiple transports. Provide a CLI for environments with a shell and an MCP server for those without, built from the same package so they always agree. Let skills pick between them so users never track which one fired.
  4. Teach workflows along with the facts. In our experience, agents changed behavior only when instructions arrived with the access. Spell out the grounding workflow (check the data source first, look up names before assuming APIs, escalate gaps) and make ungrounded generation a hard failure rather than a silent fallback.
  5. Measure quality as well as cost. Token savings are easy to chart and easy to over-read. The evidence that convinced us was component fidelity, token adherence, and predictability.

If you already have a design system, you already hold the key information AI agents need. It is just wrapped in a format built exclusively for human reading. We turned Storybook into a shared interface for humans and agents, and the work downstream in design, prototyping, and front-end engineering got faster and more reliable because both audiences now read from the same page.

We're hiring builders.