Matthew Huntsberry

Back to work

Case study · Adobe · S2A Design System

Adobe.com ships on a design system platform I built end-to-end.

Tokens, components, two MCP servers, a Figma plugin, an AI authoring toolchain, an eval harness, and a usage-telemetry pipeline — published and running in production through the Milo CMS. The other studies here are anonymised; this one I can name.

0Storybook storiesacross 43 components · 41 documented pages
0published packagestokens · MCP server · toolkit bridge · validators
0MCP toolsqueryable from any Claude Code session
0click to release tokensFigma button → gated CI → pull request
MediaCard grid from the S2A component library — editorial cards with imagery, eyebrow, headline, and CTA, rendered from the published token layer.
  • Design Systems
  • Token Architecture
  • MCP Servers
  • Figma Plugin
  • AI-Assisted Authoring
  • Evals
  • Telemetry
  • Vanilla JS
  • Nx Monorepo
  • Adobe Milo / CMS
  • Claude Code
  • WCAG 2.2 AA

The problem you’re reading about

Adobe's homepage redesign — the adobecom/milo wave — needed a component library aligned to a new brand language, and there was no infrastructure under it. No token system, no component architecture, no Figma-to-code pipeline.

The brand language was still being invented while the redesign shipped. The redesign team worked in blue sky, page by page, using whatever values a page needed. That freedom is where the brand came from and stopping it would have cost more than it saved. Engineering was moving just as fast, building straight from those designs, because waiting was not available to them either.

So none of this could be sequenced the way a design system usually is. Designers built frames; engineers re-implemented them in Milo blocks; tokens were hardcoded hex. Every component got re-decided twice, and the two versions started drifting the moment they were written.

The platform had to be built underneath two teams that could not pause for it — catching up to what had already shipped, and getting far enough ahead that the next page could start from the system instead of reinventing it.

Approach

Platform-first, tokens-first. Figma Variables are the single source of truth, and an export pipeline turns them into JSON and then semantic CSS custom properties. Component CSS references only the semantic layer — never a primitive, never a hardcoded value.

The tokens ship as a versioned package. Components are framework-agnostic vanilla JS that runs unchanged in both Storybook and Milo blocks. The entire authoring loop is wired into Claude Code through a custom MCP server and a Figma plugin.

The working loop runs backwards and forwards at once. Take what the redesign team produced, find the pattern underneath it, componentise and tokenise it, and get it back to them fast enough that the next page starts from the system rather than from scratch. The same output hands to engineering as tokens and component specs, so they are building against a contract instead of re-reading a frame.

And because neither team stops, alignment cannot be assumed — it has to be observable. The MCP server, the CSS audit, and the eval harness exist so we can see where code has drifted from the contract and where the system has not caught up yet, instead of discovering both at review.

What I rejected

  • Stencil and web components — too ceremonial for Milo's block model.
  • Figma-only token tools — everything has to be code-first and CI-publishable.
  • Milo engineers owning token loading — a governance risk, not a convenience.

Build

11 phases. Each one is a discrete deliverable that ships before the next starts — token foundation first, then the system makes everything above it possible.

  1. 01

    Token pipeline

    Figma Variables → JSON export → semantic CSS custom properties, published as @adobecom/s2a-tokens (v0.0.21 on GitHub Packages). Light and dark plus responsive breakpoint modes (sm/md/lg/xl). The semantic layer aliases primitives, so a rename, a recalibration, or dark mode never touches a component file. Consumers install via npm or a registry-free manifest and tarball path — no build step required.

    Evidence · @adobecom/s2a-tokens v0.0.21 · light/dark + sm/md/lg/xl modes · semantic layer aliases primitives · npm or manifest+tarball install

    The token pipeline

    Select any stage to trace what reaches it and what it reaches.

    tokens:synctokens:buildtokens:publishreadsaudit_cssbuildsFigma Variables669 tokens · 7 collectionsToken JSONpackages/tokens/json/Style Dictionarytransform + formatCSS outputprimitives · semantic · responsive@adobecom/s2a-tokensGitHub Packages · versionedComponents43 components · 33 spec.json contractsStorybook + axeCI gate · zero violationsMilo blocks→ adobe.coms2a-ds MCP17 tools · reads the built outputClaude Code · Cursorauthoring clients

    Ten stages and two loops back. audit_css is the MCP server checking components against the same definitions CI uses; builds is Claude Code writing components onto the Figma canvas from their contract.

    Two identical MerchCards for Acrobat Standard side by side on a dark canvas, their lower sections resolving to different surface tokens.
    One MerchCard, two surface roles, on a dark canvas. The component file is identical in both — only which primitive the semantic layer resolves to differs.
  2. 02

    Component library

    Framework-agnostic components in vanilla JS and CSS across Atoms, Molecules, Organisms, and Cards, documented by 209 Storybook stories for visual QA, autodocs, and forced-state controls. Every component exposes a decorate…(el, opts) form for Milo and a create…(opts) form for programmatic use — the same code, no adapter layer. A theme toggle drives light and dark from the real token variable modes.

    Evidence · 209 stories · 41 autodocs pages · 43 components · decorate…() for Milo, create…() for programmatic use

    Button component showcase — filled, outline, transparent, accent, and knockout styles across default, hover, active, focus, and disabled states.
    Button: styles × states, generated from the spec and captured from Storybook.
  3. 03

    Design-system MCP server

    A queryable knowledge layer so an AI agent works from real data instead of guessing: 17 tools across five categories — token resolution, component lookup, spec validation, CSS auditing, and usage validation. Published as @adobecom/s2a-ds-mcp, self-contained (it bundles a token and spec snapshot), and installable into any MCP client. Documentation cannot go stale because there is no separate documentation — the server queries the built output.

    Evidence · @adobecom/s2a-ds-mcp · 17 tools across 5 categories · bundles its own token/spec snapshot · one config line per client

    GitHub Packages listing four private packages published by adobe.com: s2a-tokens, s2a-toolkit-bridge, s2a-ds-mcp, and s2a-validators.
    Four packages, one of them the validator library — the piece that gives the agents, CI, and the eval harness a single definition of a violation.
  4. 04

    Figma plugin — S2A Toolkit

    A companion plugin that generates component docs on the canvas, adds engineering annotations and version and deprecation tags, and — the headline feature — runs a token release with one click. It also acts as the Desktop Bridge that lets Claude Code execute JavaScript inside Figma: build components, bind tokens, take screenshots. Published privately to Adobe Enterprise.

    Evidence · Published to Adobe Enterprise · canvas docs generation · engineering annotations · Desktop Bridge for Claude Code

    The S2A Toolkit command palette, listing copy Figma link, format section, filter variant set, annotate selection, clear annotations, generate component doc, request a change, and connect or disconnect the Bridge.
    Everything the plugin does, behind one shortcut — annotation, doc generation, requests, and the Bridge that lets Claude Code run inside Figma.
  5. 05

    Generating components on the canvas

    The Bridge runs in both directions. Claude Code executes JavaScript inside Figma over a WebSocket, so a component can be built on the canvas from its spec.json contract rather than drawn by hand — frames created, variants laid out, tokens bound to the right properties, and a screenshot taken back so the result can be checked against what was asked for. The pipeline's default direction is Figma to code. This is the return path, reading the same contract the other way.

    Evidence · WebSocket bridge into Figma · builds frames and variants from spec.json · binds tokens to properties · screenshots back for verification · scored in the eval harness as contract→design

  6. 06

    One-click token-release pipeline

    The release button dispatches a GitHub Actions workflow that runs the whole runbook on a clean CI runner: sync from Figma → build CSS → spot-check → bump version → changelog → package → manifest → open PR. It is serverless (the plugin calls the GitHub API directly), gated (an environment reviewer approves the run; CODEOWNERS approve the PR), and fail-loud (an unrecognised token collection stops the build instead of silently dropping it). Merging the PR auto-publishes the package.

    Evidence · Patch / minor / major chosen in the plugin · GitHub Actions dispatch · environment reviewer + CODEOWNERS gates · unrecognised collection fails the build · merge auto-publishes

    The S2A Toolkit tools panel: a component doc generator above a token release section offering patch, minor, or major, with Release Tokens and Clear token buttons.
    The whole runbook, reduced to a version bump and a button. What happens after the click is gated by a reviewer and CODEOWNERS.
  7. 07

    Contribution and triage

    The same plugin is how work gets requested. Anyone in the org opens it from Figma to report a bug, request a change to an existing component, or propose a net-new one. The form captures the selected node, the token, the file and the author automatically, and makes the requester type it — new token, token change, new component, component change, bug — and rank it as nice to have, needed soon, or blocking. The submission opens a triage-ready GitHub issue; we triage it and either decline it or accept it into Jira, with the issue linked to the ticket so the requester can follow it without an account in our tracker. The design-system team reviews the queue weekly, and whether something is accepted is not my call alone — that sits with the review group. Contribution runs through the tool designers already have open, rather than a process they have to be taught and then reminded about.

    Evidence · Requests filed from inside Figma · node, token, file and author captured automatically · typed and prioritised at source · reviewed weekly · opens a GitHub issue · accepted into Jira

    The S2A Toolkit request form, pre-filled with the selected node, token, file and author, offering request types of new token, token change, new component, component change or bug, a use-case field, and a priority of nice to have, needed soon, or blocking.
    The request arrives already knowing what was selected and who selected it. Typed and prioritised at source, so triage is a decision rather than an interview.
  8. 08

    Eval discipline

    Because generation is probabilistic, I built the measurement discipline around it. Golden datasets pin characteristics rather than a frozen answer, each case carrying difficulty and positive or negative polarity, scored against the component spec.json contract in two directions: design→code and contract→design. Scoring is deterministic first — error-code scorers for token-hierarchy violations, spec conformance, and axe rule IDs — before pixel-diff, before LLM-as-judge.

    Evidence · Golden datasets pin characteristics, not answers · deterministic scorers before pixel-diff before LLM-judge · shared validator lib gives agents, CI, and evals one definition of a violation

  9. 09

    Library analytics — the design side

    Figma's Library Analytics say how the design side is really using the system: weekly component insertions and detachments across every file consuming the S2A library, alongside live Milo token-compliance data. Detachments are the more useful half. A component with a rising detachment count is nearly right and being worked around, which is a design signal you will not find anywhere in the codebase.

    Evidence · Weekly insertions and detachments across the S2A library · live Milo token-compliance scan · regenerated from a snapshot script, not hand-maintained

    S2A Design System analytics dashboard showing 40 components built in S2A, 2 officially mirrored into Milo, and 71 Milo files adopting tokens unofficially, above a library adoption trend charting weekly component insertions and detachments inside Figma design files.
    Figma Library Analytics and Milo token compliance in one view. 71 Milo files pulling tokens against 2 components officially mirrored — adoption arriving through the token layer first, which is the order the migration was planned in.
  10. 10

    Tooling telemetry — the code side

    The tools report on themselves separately. The MCP server, the plugin, and the bridge each emit anonymised usage events (which action, when, ok or error; a one-way-hashed id; opt-out everywhere; nothing sensitive) to a Cloudflare Worker and Durable Object collector that aggregates them live. This is how we learn which of the 17 MCP tools earn their keep and which are dead weight, so what gets built next is grounded in real use rather than in what feels important.

    Evidence · Cloudflare Worker + Durable Object collector · one-way-hashed ids · opt-out everywhere · off unless pointed at a collector

  11. 11

    Production integration (Milo)

    Components ship into the Milo CMS as block implementations. Milo is older infrastructure — hand-written HTML elements — so converting it to componentised atoms is a long migration, and the sequencing matters: tokens are adopted and aligned first, because a shared value layer is the precondition for everything after it. That is why token adoption runs well ahead of component mirroring, and why it should. The vanilla-JS decoration pattern matches Milo's block model exactly, so there is no adapter layer, and token CSS loads as a stylesheet dependency. The context/milo submodule tracks adobecom/milo for integration parity — Figma → tokens → components → Milo blocks → adobe.com, end to end.

    Evidence · Milo block implementations · no adapter layer · token CSS as a stylesheet dependency · context/milo submodule tracks upstream

    HubRouter organism — the 'Everything you need to make anything' section, a horizontally scrolling row of category cards for Creativity and design, Content creation, PDF and productivity, Marketing content, and Students and teachers.
    HubRouter as it renders on adobe.com — a page-level block built from the same component code that runs in Storybook.

Working with the teams

I built the platform, but not in a vacuum. These are the standing relationships it runs on.

  1. The redesign team

    They work in blue sky, and they should — the brand language came out of that freedom, not in spite of it. What arrived was frames using whatever values the work needed. My job is to find the pattern underneath and back it into the token architecture, rather than hand them a locked palette and ask them to stay inside it. Designs come to me with arbitrary values and go back as tokenised components, with the reasoning attached. The education is the deliverable, not a side effect: a system nobody understands is a system people detach from, and the detachment count is one of the things I watch.

  2. The block library team

    A block library is built on top of the S2A foundations library, which makes them the system's first real consumer. That is the reason the component API is a contract rather than a convention — anything I change lands in their build. Their constraints shape what foundations ships, and a change that would be elegant for me and expensive for them is not a change worth making.

  3. Milo engineering

    Milo engineers own the CMS these components ship into. Keeping token loading in the platform rather than in their hands was a conversation, not a merge — governance from where I sit, one less thing to own from where they sit. Framing it as the second thing is what got it agreed.

  4. The review group

    Changes into the system go through a standing group: design system leadership, me, and a representative from each surface. A request lands only if the surfaces agree it should. That keeps the system from turning into one team's list of preferences, and it keeps me from being the person who says no — the answer comes from the group that has to live with it.

How the system resists drift

Every design system decays. Values get nudged for one screen, a component is nearly right so someone works around it, an agent invents a plausible token that never existed. You cannot prevent that. You can only make it expensive and visible, and the whole platform is an argument about where to put the instrumentation.

Each layer below catches drift earlier than the one after it, and earlier is cheaper. Authoring-time beats review-time beats production.

  1. Design authoring

    Figma Variables as the only upstream source, the plugin, and teaching the reasoning back rather than handing over a palette. Where a component can be generated onto the canvas from its contract, the Figma library and the code library cannot disagree by hand at all.

    Otherwise designers invent values engineering then has to honour.

  2. Code authoring

    The MCP server as a live oracle, with audit_css running against the built output. An agent answers from the build rather than from memory.

    Otherwise hardcoded values reach PR review, where catching them is expensive and personal.

  3. Merge

    Storybook and axe as a CI gate at zero violations, plus spec.json conformance.

    Otherwise a violation lands in main and becomes precedent for the next one.

  4. Generation

    Golden datasets that pin characteristics rather than answers, scored deterministically before pixel-diff and before LLM-as-judge.

    Otherwise probabilistic output is plausible, wrong, and unmeasured.

  5. The field, after ship

    Figma detachment counts and a live Milo token-compliance scan.

    Otherwise adoption decays quietly and you find out a year late.

  6. Scope

    The review group — leadership, me, and one representative per surface.

    Otherwise the shared system becomes one team's list of preferences.

The shared validator library is the part that makes the rest hold. It gives the agents, CI, and the eval harness one definition of a violation instead of three subtly different ones — and the gaps between three definitions are exactly where drift gets in, because every individual check still passes.

Detachment count is the only leading indicator in the list. Everything else catches something already wrong. A rising detachment count catches a component that is nearly right and being worked around, which is drift before it becomes a violation — and it originates in design rather than in code, which is the seam this role exists to hold.

Outcomes

  • 71 Milo files adopting S2A tokens — against 2 components officially mirrored. The migration is deliberately tokens-first: aligning values is the precondition for componentising hand-written HTML, so the token layer is where adoption shows up first.
  • 669 tokens across 7 collections, flowing one direction from Figma Variables to a versioned package, released by a button instead of a runbook.
  • 209 Storybook stories across 43 components and 41 documented pages, covering the homepage redesign at every breakpoint.
  • Contribution runs from inside Figma: a request becomes a GitHub issue, gets triaged into Jira, and stays linked back to the requester — no tracker account required to follow it.
  • Four published packages — tokens, MCP server, toolkit bridge, and the shared validator library — plus a Figma plugin published to Adobe Enterprise.
  • 17 MCP tools give Claude Code live query access to the real system, so token violations are caught at authoring time rather than in PR review.
  • Both sides of the bridge are measured: Figma Library Analytics for design-side usage, and an anonymised, opt-out telemetry collector for the tooling.
  • An eval harness — golden datasets, deterministic scorers, and a shared validator lib — so quality is measured rather than assumed.
  • Zero framework dependency in production: the same vanilla JS runs in Milo, in Storybook, and in whatever comes next.
  • Mentored designers from zero to a Claude-assisted Figma workflow within sprint cadence.

Key Decisions

9 architectural decisions, and the reasoning behind each.

  1. The semantic token layer is a hard contract — no primitive reaches component CSS.

    The indirection is the only thing that keeps a token system this size maintainable by a small team. A rename or a recalibration is a token change, not a component sweep.

  2. Vanilla JS factory functions instead of web components.

    Matches Milo's block decoration model exactly, which eliminates the two-implementations problem — no adapter layer to keep in sync with the thing it adapts.

  3. The MCP server is a live oracle, not a wiki.

    It queries the built output, so it cannot go stale. Onboarding a new consumer is one config line rather than a documentation pass.

  4. Deterministic-first evals, backed by a shared validator library.

    Most of what a design system must check is code-checkable. Measuring it cheaply and identically in the agents, in CI, and in the evals means one definition of a violation instead of three.

  5. Telemetry is private by default — anonymised, opt-out, and off unless pointed at a collector.

    Adoption data is worth having, but not at the cost of trust. The version that respects that is the version people leave switched on.

  6. Tokens before components, on a legacy CMS.

    Milo is hand-written HTML. Componentising it is a multi-quarter migration, but aligning its values is not — and every component that lands later is cheaper once the value layer already agrees. Adoption numbers read low on components and high on tokens because that is the order the work was sequenced in.

  7. The system has a review group, not an owner.

    Leadership, me, and one representative per surface. Distributing the yes-or-no across the teams that have to live with it is what stops a shared system from becoming one team's preferences — and stops the maintainer from being the bottleneck everyone routes around.

  8. Contribution goes through the tool people already have open.

    A request filed from inside Figma becomes a GitHub issue without anyone learning a new process. Governance that lives where the work happens gets used; governance that lives in a wiki gets bypassed.

  9. Figma Variables are the single upstream source.

    One system rather than a Supernova or Token Studio sync with its own failure modes. There is no second place for a value to disagree with itself.

What this proves

This is not a component library with tooling bolted on. It is a design system platform: tokens, components, two MCP servers, a Figma plugin, an AI authoring toolchain, a gated release pipeline, an eval harness, and a telemetry loop.

Small enough to stay maintainable, documented enough that designers and engineers build on it without needing to know how it is put together, and already running in production at adobe.com.

The part I would carry to the next system is the constraint, not the stack. It was built underneath a redesign that could not pause, for two teams moving faster than it could be finished, which is why every layer of it is instrumented. A system nobody can measure against is a system that has already drifted and does not know it.

The token → component → tooling → AI-authoring → measurement stack is a repeatable pattern, and the surrounding case studies are what it looks like at other scales.