Building the Prerender.io Design System, Wave by Wave

B

This is Part 2 of a three-part series on building the Prerender.io Design System – a front-end foundation built with Claude Code and Figma, designed to serve as a single source of truth across all Prerender.io products.
Part 1: From Figma to Front-End Without Losing Your Mind
Part 3: What Collaborating with an AI Dev Actually Taught Me (coming soon)


Once the workflow was in place, the next question was how to structure the actual build. You might just point Claude at a Figma file and say “build this.” But you won’t like what comes back.

What worked was treating the whole thing like a construction project. Foundation first, then structure, then fittings. We called each phase a wave. The following article helped me a lot to have a proper launch: How to send your app code to Figma using Claude Code

Why tokens before components?

Before writing a single component, we extracted the design tokens from the Figma library. Colours, spacing, typography scale, border radii, shadows, breakpoints. All of them were codified into two files: tokens.css for CSS custom properties, and antdTheme.ts mapping those values into Ant Design’s ConfigProvider. Building everything on top of Ant Design gave a proper structure (from simple elements up to sophisticated visual structures).

This was the most important decision of the entire build.

Once the tokens were in place, every component had a shared vocabulary. When a colour needed to change, it changed in one place and propagated everywhere. Without this step you end up with hundreds of hardcoded hex values scattered across dozens of files, and updating anything becomes archaeology (Claude is very efficient at fixing such things, but it’s a waste of tokens).

It also made the collaboration with Claude significantly cleaner. Instead of describing colours by value in every prompt, we could just say “use the primary token”, and Claude reached for the right thing.

antdTheme.ts – the same tokens mapped into Ant Design’s ConfigProvider, so every component inherits them automatically.

Eight waves, one component library

We split the build into eight waves:

  • Wave 1 – App shell (AppLayout, AppHeader, AppSider) + primitives (Button, Badge, Tag, Spin, Progress, Statistic, Empty)
  • Wave 2 – Forms (Input, Select, Radio, Checkbox, Switch, Upload, Form)
  • Wave 3 – Navigation (Breadcrumb, Dropdown, Pagination, Steps, Tabs)
  • Wave 4 – Feedback (Alert, Modal, Tooltip, Popconfirm, Message, Notification)
  • Wave 5 – Data display cards (StatCard, ChartCard, CustomCard)
  • Wave 6 – Table with responsive mobile variant
  • Wave 7 – Charts (Line, Bar, StackedBar, Pie/Donut – all Chart.js backed)
  • Wave 8 – Extended components (DatePicker, Slider, Skeleton, Result, Drawer)

Each wave had a written plan upfront: what components to build, what props they’d need, which Figma nodes they mapped to. I shared that plan with Claude and asked it to write its own version before executing. That extra step matters more than it sounds.

The wave plan: your most important file

Here’s the thing nobody tells you about working with Claude on a project this size: context gets interrupted. A session ends. You come back the next day. Claude has no memory of where you left off.

Without a plan document, you’re starting from scratch every time.

What we did was keep a single markdown file: the wave plan; that tracked the full state of the build. Every wave listed its components with a clear status: pending, in progress, or done. Every component listed the Figma node it came from. Any decisions made mid-wave, any gotchas discovered, any partial work left open, all noted in the same place.

A wave entry looked roughly like this:

## Wave 5  -  Data display cards  [STATUS: in progress]

- [x] StatCard  -  Figma node 1045:2310  -  done
- [x] ChartCard  -  Figma node 1604:735  -  done
- [ ] CustomCard  -  Figma node 1045:3190  -  pending

### Notes
- StatCard: progress bar uses strokeColor #2da01d, trailColor rgba(0,0,0,0.06)
- ChartCard: three variants  -  default, sub-metric, additional-numbers

When I resumed a session after a break, the first thing I’d do was point Claude at that file: “Read the wave plan, find the first pending item, and continue from there.” Claude would orient itself, pick up the thread, and carry on.

This single habit: keeping the plan document up to date and treating it as the session handoff, made the whole build feel coherent. Without it, each session would have been a round of re-explaining context that Claude had already processed the day before.

Share the plan first, always

When Claude is handed a large task all at once, quality drops. Not because of any emotional state (I thought it feels anxious about the huge workload ahead of them), it’s a resource constraint. Claude allocates attention across the full scope of a task, and the larger the scope, the less attention each individual component receives. It starts approximating. Ambiguity becomes permission to guess. And you don’t want that in a pixel-perfect Design System.

The plan-first habit costs five minutes and regularly saves hours. It creates a moment of alignment before any code is written. More importantly, it’s the moment where misunderstandings surface, before they’re baked into twelve components.

An excerpt from the wave plan document – each wave tracked with per-component status, Figma node references, and session notes.

Pulling from Figma: smaller is always better

Almost any Figma design taller than about 1000px is too much to digest cleanly (sometimes even 500px). Claude reaches for a screenshot, works from a flattened image, and the output suffers for it.

get_design_context is almost always the better choice. It returns structured data: component names, variant states, layout properties; rather than a pixel rendering. But it only works well when you point it at a specific node, not an entire page.

The approach that worked: identify individual sections of a screen (header, metric cards, table, footer), point Claude at each node separately, build component by component (Sometimes I put the frames outside of the initial parent frame, just for Claude to concentrate on this without the noise of the parent node). Slower to set up, but consistently more accurate, and easier to fix when something is off.

For icons and visual assets specifically, screenshots were occasionally useful as a side-by-side comparison. But always as a supplement, never as the primary source.

What’s the asset rule I wish I’d set earlier?

Always ask Claude to download assets locally. If you don’t, it will reference Figma CDN URLs directly in the code. Those URLs expire in seven days. You won’t notice until you’re doing a demo and half the images have gone blank.

Same applies to any external asset: logos, illustrations, icon files. If it matters to the project, it lives in the repo.

Fix mistakes the moment you see them

Small inconsistencies: a colour that should reference a token but doesn’t, a padding value that’s slightly off scale, left alone; they get copied into the next component. By wave five, you’re chasing the same mistake across a dozen files.

Correcting immediately is more efficient than it sounds. Claude picks up the correction and applies it going forward. Leave it too late, and the fix becomes a refactor.

Why give accessibility its own wave?

I hadn’t planned for a dedicated WCAG 2.2 AA audit. We ran it as a separate pass after the main build, and it caught a surprising number of things.

Colour contrast failures on secondary text. The difference between rgba(0,0,0,0.45) and rgba(0,0,0,0.62) sounds trivial, it’s the difference between failing and passing AA. Missing focus rings on some buttons. Chart canvases with no accessible description. Touch targets below the 24px minimum (How have I missed it?).

Running the audit as a named, planned wave with a document tracking each fix produced much cleaner results than trying to bake accessibility in component by component. Claude could focus on one class of issue at a time, which kept the fixes on the topic.

Do this at least twice during a project. Not just once at the end.


If I had to compress this into one idea: the structure you put in before building determines the quality of what comes out. Extract the tokens first: everything else is downstream of that decision. Keep a live wave plan and treat it as the session handoff, not documentation. Share the plan with Claude before any code gets written. Point it at individual Figma nodes, not full pages. Fix inconsistencies as you spot them, not at the end. And run accessibility as a dedicated pass: it will find things a component-by-component approach misses.

Next: what working closely with Claude actually taught me about prompting, scope, expertise, and what changes when you collaborate with AI on something this structured (coming soon).

About the author

Lucas

UX Lead and AI Transformation Consultant
20+ years shaping B2B SaaS and digital products. Focused on AI-powered design, scalable UX, and turning complex business needs into simple, high-impact user experiences.

By Lucas