← Blog

AI agents

Claude als designsysteem: een Figma-handoff vervangen

Een Eindhovense studio van 22 mensen ruilde Figma-handoffs in voor een Claude-agent die Tailwind React-stubs schrijft die de front-end ook echt mergt. Zo bouwden we hem.

Jacob Molkenboer· Oprichter · A Brand New Company· 7 jun 2026· 9 min
Messing relais, gevouwen papierstrook met groen plakkertje, linnen lint op ivoorkleurig papier.

Donderdagmiddag in een studio aan de Vestdijk in Eindhoven. De lead designer, Famke, heeft net een checkout-flow afgemaakt in Figma. Vier breakpoints, twaalf states, een sticky samenvatting, een kortingsveld dat op mobiel inklapt. Ze exporteert de frames, plakt een Loom in Slack, tagt twee front-end devs, en typt de zin die elke designer in dit land ooit getypt heeft: laat me weten als iets onduidelijk is.

Drie dagen later komt de PR terug. De padding is op twee plekken 4px verkeerd. De empty state ontbreekt. Het kortingsveld klapt juist uit in plaats van in. Famke laat negen comments achter. De devs zuchten. Iedereen doet zijn werk; de handoff is het probleem.

Dit is de studio die ons vroeg het op te lossen. Tweeëntwintig mensen, vooral product designers, twee front-end engineers in dienst, de rest freelance. Ze leveren voor Nederlandse banken en een paar energieleveranciers. Figma is de bron van waarheid. React + Tailwind is de bestemming. De ruimte daartussen is waar hun marge zit, en waar die weglekt.

We hebben zes weken besteed aan een Claude-gedreven component-spec-agent. Hij vervangt de designers niet. Hij vervangt de engineers niet. Hij vervangt de handoff: de Loom, de Slack-tag, het heen-en-weer over of een chip nou een Badge of een Tag is. Een half jaar later mergt het front-end team grofweg 70% van de stubs die de agent oplevert met alleen cosmetische aanpassingen. Dit is het verhaal van hoe het werkt en wat we onderweg fout deden.

Het echte probleem met Figma-handoffs

Eerlijk gezegd: Figma is een tekenprogramma waar een component-model aan vast is gegroeid. React is een component-model waar een styling-systeem aan vast is gegroeid. Geen van beide is ontworpen om de ander te beschrijven. Tokens helpen. Auto-layout helpt. Dev Mode helpt. Geen daarvan dicht het gat, want het gat is niet visueel, het is semantisch.

Als een designer een kaart tekent met een header, een body en een CTA, heeft ze honderd impliciete keuzes gemaakt. Is de CTA een Button of een Link die als Button is gestyled? Heeft de kaart een hover-state op touch-apparaten? Is de header een h3, of visueel zo gestyled maar semantisch een div? Figma kan die vragen niet beantwoorden. De designer wel, maar alleen als je het vraagt, en vragen kost een meeting.

De Hacker News-thread van vorige week met de titel "I design with Claude more than Figma now" haalde 106 punten omdat die ervaring nu gemeengoed is. Een groeiend deel van designwerk is conversatie: hoe moet dit heten, welke states heeft het nodig, wat gebeurt er op kleine schermen. Figma is het canvas; het denken gebeurt elders. Wij namen die observatie en bouwden er een pipeline omheen.

Wat de agent precies doet

De agent is geen code-generator in de Vercel v0-zin. Hij kijkt niet naar een Figma-frame en probeert te raden hoe de React eruit moet zien. Hij doet iets smallers en bruikbaarders: hij produceert een component-specificatie, en daarna een Tailwind-ready stub die past bij de bestaande componentenbibliotheek, naamconventies en accessibility-regels van de studio.

De input bestaat uit drie dingen:

  • Een Figma node-ID, opgehaald via de Figma REST API.
  • De components.json van de studio: een manifest van elke bestaande component, zijn props en zijn toegestane varianten.
  • Een korte vrije brief van de designer, geschreven alsof ze het aan een junior dev uitlegt.

De output is een pull request. Geen draft, geen gist, een echte PR tegen de monorepo van de studio met drie bestanden: de component, een Storybook-story, en een Vitest-bestand dat het props-contract test. De PR-beschrijving bevat de spec die de agent eerst schreef, zodat de reviewer de intentie kan controleren voordat hij de code leest.

De spec-first loop

Wat dit liet werken, na twee mislukte pogingen, was de agent dwingen om de spec te schrijven vóór de code. Claude is goed in React. Claude is uitstekend in een heldere beschrijving van een component schrijven, en daarna code die bij die beschrijving past. Sla je de beschrijving over en ga je direct naar code, dan krijg je geloofwaardig ogende React die binnen twintig regels afdrijft van de conventies van de studio.

De spec is een Markdown-document met een vaste structuur:

## CheckoutSummary

**Purpose**: Sticky order summary on the right of the cart page (desktop), collapsing into a bottom drawer on mobile.

**Props**
- items: LineItem[]
- discount?: Discount
- onApplyDiscount(code: string): Promise<Result>
- variant: 'desktop' | 'mobile'

**States**
- empty (no items)
- with-items
- with-discount-applied
- discount-error
- loading (during applyDiscount)

**Accessibility**
- Sticky region uses role="complementary"
- Discount input has aria-describedby pointing at error text
- Drawer is a focus-trapped dialog on mobile

**Out of scope**
- Currency formatting (use existing <Money/>)
- Tax calculation (parent passes final amounts)

Dat document is wat de designer reviewt. Niet de React. De React wordt in een tweede ronde uit de spec gegenereerd, met de spec als system-context. Klopt de spec niet, dan fixt de designer de spec, en wordt de code opnieuw gegenereerd. Engels reviewen gaat sneller dan JSX reviewen, en designers kunnen dat ook echt.

Belangrijkste les

Genereer eerst de spec, daarna de code. De spec is wat de designer reviewt; de code is wat de agent schrijft. Klap je die twee stappen samen, dan stort de loop in.

Hoe het components.json-manifest hem eerlijk houdt

Het grootste risico bij code-genererende agents is drift. Ronde één krijg je een schone component. Ronde vijftig heb je drie subtiel verschillende Button-implementaties en een Toast waarvan niemand zich herinnert wie hem schreef. Wij hebben dit opgelost met een manifest dat de agent bij elke run moet raadplegen.

Het manifest wordt elke nacht uit de echte codebase gegenereerd, met een kleine AST-walker. Voor elke geëxporteerde component noteert hij het bestandspad, de props met hun types, de varianten die de component accepteert, en een eenregelige beschrijving uit een JSDoc-blok boven de export. Het ziet er zo uit:

{
  "Button": {
    "path": "src/ui/Button.tsx",
    "props": {
      "variant": ["primary", "secondary", "ghost", "danger"],
      "size": ["sm", "md", "lg"],
      "loading": "boolean",
      "icon": "ReactNode"
    },
    "description": "Primary action element. Never use for navigation; use Link."
  },
  "Link": { "...": "..." }
}

Voordat de agent één regel schrijft, krijgt hij het volledige manifest, met de instructie om bestaande componenten te hergebruiken waar de spec dat toelaat. Heeft een CheckoutSummary een button nodig, dan importeert hij Button. Vraagt de spec om een chip en bestaat Chip al, dan gebruikt hij Chip. Past niets, dan moet hij dat in de spec zeggen, in een sectie genaamd New components required, en stoppen. Een mens beslist of de bibliotheek wordt uitgebreid.

Die laatste regel is wat het vertrouwen van het front-end team won. De agent verzint geen componenten. Dat kán hij niet. Het manifest is zijn wereld.

De Tailwind-beperking die iedereen onderschat

Tailwind is duizend atomaire keuzes per component. Een naïeve agent kiest text-gray-700 waar het designsysteem text-ink-secondary heeft. Hij gebruikt p-4 waar de spacing-token p-card is. Elke fout is klein en elke fout vraagt een handmatige fix, en die handmatige fixes maken de tijdwinst kapot.

We hebben dit opgelost door het standaard Tailwind-thema volledig uit de context van de agent te halen en te vervangen door de tailwind.config.ts van de studio, samengevat in een token-referentietabel. De agent weet letterlijk niet wat text-gray-700 betekent in zijn context window, want de Tailwind-defaults staan er niet. Hij kent alleen text-ink-primary, text-ink-secondary, bg-surface-raised, enzovoort. Dit is dezelfde truc waarmee je elk LLM een domeintaal laat spreken: zeg niet dat hij voorkeur moet geven aan het domein, verwijder het alternatief.

Waarschuwing

Laat je agent geen vanilla Tailwind-utilities zien als je een design-token-override hebt. Onder druk grijpt hij naar de defaults, zeker als de prompt lang wordt. Haal ze uit de context, raad ze niet alleen af.

Wat we de eerste twee keer fout deden

De eerste versie was één prompt: hier is het Figma-frame, hier is het manifest, schrijf de component. Het werkte voor triviale componenten en faalde bij alles met state. Hij bleef vooral prop-namen verzinnen die lekker lazen maar niet matchten met de conventies van de studio (isLoading vs loading, onClickApply vs onApplyDiscount). Pull requests werden bikeshedding-sessies over naamgeving.

Versie twee voegde een linter-pass toe: genereer de code, draai dan een custom ESLint-plugin die prop-namen en import-volgorde controleert. Dat ving de bikeshed-problemen, maar miste de structurele. We verbrandden ook tokens met meerdere round-trips als de agent hele bestanden herschreef om één prop-naam te fixen.

Versie drie is de versie die bleef hangen. De pipeline bestaat nu uit vier aparte stappen, elk met een smalle taak:

  1. Lezen: trek de Figma-node, het manifest en de brief van de designer in een context-bundle.
  2. Spec: produceer de Markdown-specificatie. De designer geeft hier akkoord, in een Slack-thread aangestuurd door een kleine bot. Nog geen code.
  3. Stub: genereer de React + Tailwind + Storybook + Vitest-bestanden, met het manifest en de goedgekeurde spec als system-context.
  4. Gate: draai ESLint, TypeScript, de testsuite en een Playwright visual-diff tegen de geëxporteerde PNG van het Figma-frame. Faalt iets, dan krijgt de agent de error-log en één retry. Faalt de retry, dan opent hij de PR als draft met de fouten erbij en tagt een mens.

De visual diff is bij benadering. We accepteren alles onder een drempel van 4% pixel-verschil, wat ruim klinkt maar overeenkomt met wat mensen in de praktijk opmerken. De stack van Playwright + pixelmatch is goed gedocumenteerd in de Playwright snapshot guide, en de vergelijking schrijven was een paar middagen werk, geen heel project.

Wat het front-end team er werkelijk van vindt

De front-end leads waren de meest sceptische groep, en dat is precies wat je verwacht en wat je hoopt. Ze zijn sinds 2017 gebrand op tools die React uit designs beloofden. Hun voorwaarde voor adoptie was simpel: de PR moet er een zijn die we van een junior dev op een goede dag zouden accepteren. Dat betekende: hun imports matchen, hun prop-conventies, hun test-patronen, hun commit-bericht-format.

Week één lukte dat niet. Week vijf wel, en pas nadat we ze de prompts voor de spec-naar-stub-stap zelf lieten schrijven. Hun versie was botter dan de onze. Er stonden dingen in als importeer Button nergens vandaan behalve uit @studio/ui en props moeten alfabetisch. Die regels maakten de output van de agent niet meer te onderscheiden van die van henzelf.

Het getal dat voor hen telt: de gemiddelde PR van de agent kost nu 11 minuten reviewen en 23 minuten om te mergen, tegen 90+ minuten voor een handgebouwde component met vergelijkbare scope. Ze hebben in vier maanden geen kaart-achtige component meer vanuit een Figma-frame hoeven schrijven.

Waar het niet werkt

Op drie plekken. Ten eerste: alles met niet-triviale animatie. Tailwind met een paar varianten doet de agent prima; Framer Motion-sequenties met shared layouts niet. Die sturen we naar een mens en daar doen we niet moeilijk over.

Ten tweede: alles met state dat de data-laag van de studio raakt. De agent heeft geen model van de API. We hebben hem OpenAPI-specs als context gegeven en de output werd slechter, niet beter; hij begon endpoints te verzinnen die logisch klonken maar niet bestonden. We hebben hem teruggetrokken naar props-only en de kwaliteit kwam terug.

Ten derde: de eerste component in een nieuw product. Het manifest is leeg of dun, de conventies liggen nog niet vast, en de agent heeft niks om op te leunen. We zetten hem in vanaf component drie of vier, zodra de patronen er staan.

De scaffolding, niet het model

Wie agents echt in productie heeft gebracht, kent het patroon: het model doet er minder toe dan de scaffolding eromheen. Onze agent draait nu op een frontier-model en draait over een half jaar op een ander. De spec-template, het manifest, de gate-stap, de pipeline van vier stappen: dat zijn de delen die zich opstapelen. Het model is inwisselbaar.

Dit is ook waarom we geen Figma-plugin hebben gebouwd. Een plugin leeft in de runtime van Figma en het permission-model van Figma. De pipeline leeft in de CI van de studio, en dat is waar de conventies, de linters en de tests al wonen. Het werk opzoeken waar het neerkomt verslaat het in een nieuwe UI verpakken.

Toen we de spec-en-stub-pipeline voor die Eindhovense studio bouwden, was Tailwind-drift waar we op stuitten; we hebben dat opgelost door het standaardthema uit de context van de agent te halen en te vervangen door de tokens van de studio, en dat is de truc die de merges uiteindelijk rustig maakte. Wil je zien hoe wij AI-agents zoals deze tegen echte codebases bouwen, dan is die link het beginpunt.

Het kleinste wat je vandaag kunt doen: open je designsysteem-repo, schrijf met de hand een components.json-manifest van dertig regels voor je tien meest gebruikte componenten, en lees het terug. Beschrijft het niet hoe je team echt bouwt, dan doet je agent het ook niet.

Kern

Genereer eerst de component-spec, daarna pas de code. De designer reviewt de spec; de agent schrijft de stub. De handoff verdwijnt zodra het artefact in het midden taal wordt.

FAQ

Waarom eerst een geschreven spec genereren en pas daarna de code?

Designers lezen Engels sneller dan JSX. De spec is het reviewoppervlak; de code is er een deterministische vertaling van. Een fout in de spec vangen is grofweg tien keer goedkoper dan hem in een PR vangen.

Vervangt dit het front-end team?

Nee. Het vervangt het handoff-gesprek. Front-end engineers reviewen nog elke PR, schrijven de lastigere componenten, beheren de data-laag en bepalen de conventies waar de agent zich aan moet houden.

Wat als de agent een component verzint die niet bestaat?

Dat kan hij niet. Het manifest wordt elke nacht uit de echte codebase gegenereerd, en de agent heeft de opdracht om te stoppen en een nieuwe component aan te vragen als niets in het manifest past. Een mens beslist of de bibliotheek wordt uitgebreid.

Waarom dan niet gewoon een Figma-to-code-plugin gebruiken?

Plugins leven in Figma en kunnen je linters, tests en visual diffs niet draaien. Een CI-pipeline ontmoet het werk waar het al neerkomt en handhaaft dezelfde gates waar de rest van het team tegen aan levert.

Hoe voorkom je dat de agent richting vanilla Tailwind afdrijft?

Haal het standaard Tailwind-thema volledig uit zijn context. Geef hem alleen de tokens van de studio. Hij kan niet kiezen wat hij niet ziet, en dat is betrouwbaarder dan hem vragen om zich te gedragen.

ai agentscase studytoolingworkflowarchitectureprocess automation

Iets bouwen?

Start een project