The prompt sounds small: “Forty product teams ship on the same web app, and every one has built its own button. Design a system that stops that.” A junior candidate opens by writing a <Button>. That is the trap. Nobody pays a staff engineer to write a button. This round is about the machinery around the button, the part that decides whether those forty teams still use your version a year from now or quietly fork their own.
The design-system round shows up at companies with a real web platform group behind them: Meta, Shopify, Atlassian, Stripe, and most orgs large enough to fund a dedicated component-library team. Budget 45 to 60 minutes. It is a conversation, not a coding screen. The interviewer is listening for how you handle tokens, theming, accessibility defaults, versioning, and the bundle every team inherits the moment they add your package to their package.json.
Pin down what “design system” means here
The phrase covers three things that do not ship as one unit, and confusing them is the fastest way to lose the room. There is the token layer (the values), the component library (the actual code), and the governance around adoption (docs, review, migration support). Spend the first few minutes asking which one matters most for this exercise. Ask how many brands the system dresses, whether it is web-only or also native, and whether product teams live on one framework or three. A single React app and a multi-brand, multi-framework estate lead to completely different designs, and jumping straight to component code signals you have only built the small version.
GreatFrontend’s RADIO scaffold (requirements, architecture, data model, interface, optimizations) keeps you on track here, as long as you don’t recite it like a checklist. The interviewer cares that you gather constraints before drawing boxes, not that you name the acronym. The follow-ups are predictable once you know the shape of the round. Expect some subset of these:
- “A new brand needs its own colors and typeface. What changes, and what ships without a rebuild?”
- “How do you stop forty teams from overriding your components with random CSS?”
- “A team is three major versions behind. How did that happen, and how do you get them current?”
- “Someone imported one icon and their bundle grew 80 KB. Walk me through why.”
Tokens are the contract, components are just consumers
Design tokens are the layer that makes everything else tractable, so start there. The mistake is treating tokens as a flat list of color and spacing values. Serious systems use three tiers, and being able to explain why is a strong signal.
| Token tier | Example name | What it holds | Who changes it |
|---|---|---|---|
| Primitive (base) | blue-600, space-4 |
Raw palette and scale values, brand-agnostic | Design system core team, rarely |
| Semantic (alias) | color-action, color-danger, surface-raised |
Intent mapped onto primitives; the layer each theme reassigns | Core team, once per theme or brand |
| Component | button-bg, input-border |
Component-scoped values for the rare real exception | Individual component owner |
The middle tier is the one that earns its keep. When a component reads color-action instead of blue-600, you can repoint color-action to a different primitive per theme and every button, link, and focus ring moves with it. No component code changes. That indirection is the whole reason multi-brand theming stays sane instead of turning into a find-and-replace across the codebase.
Theming without forking the library
Someone will ask how a second brand, or dark mode, or a high-density enterprise layout gets applied. The weak answer passes a theme object down through React context and reads it in every component. That works until server rendering, then you are shipping two renders or fighting hydration mismatches. The stronger answer keeps the semantic tokens as CSS custom properties and swaps them with a selector.
:root { --color-action: #2563eb; --surface-raised: #ffffff; }
[data-theme="dark"] { --color-action: #60a5fa; --surface-raised: #1e293b; }
.button { background: var(--color-action); }
Theme switching becomes a single attribute on <html>. No re-render, no context plumbing, and it survives static rendering because the variables resolve in the browser. Build-time approaches (Sass compilation, a CSS-in-JS ThemeProvider) still have their place and win when you want zero runtime cost or a fully static per-brand bundle, but they force a rebuild for every brand and can’t switch themes live. Name the tradeoff you are taking and why.
Accessibility has to live in the primitives
If accessibility is something each of the forty teams has to remember, it will not happen. The reason you own the primitives is so correct behavior gets inherited. A menu built on your system should already trap focus, support arrow-key navigation, close on Escape, and expose the right ARIA roles, without the product engineer thinking about any of it.
This is why most current systems build on headless primitives like Radix UI or React Aria (Adobe’s library) rather than hand-rolling every widget. Those handle the genuinely hard parts, focus management, roving tabindex, and screen-reader semantics, and let you own the styling. Say that you would enforce WCAG 2.2 contrast (4.5:1 for body text) in the token pipeline itself, so a designer cannot ship a semantic color pair that fails. Style Dictionary can run that check at build time. An interviewer who works on a platform team will perk up at that, because they have lived through the bug where a new brand color quietly broke contrast across every app at once.
Versioning is what actually decides adoption
Here is the part weaker answers skip and the part that sinks real design systems. You can build a beautiful library, and if upgrading it hurts, teams pin an old version and you now maintain six. Walk through semantic versioning the way it actually bites: patch and minor releases have to be safe to take blind, which means your definition of “breaking” includes visual and behavioral changes, not merely the API signatures. Changing a default padding is a major bump even though TypeScript won’t complain about it.
In a monorepo, reach for independent versioning with a tool like Changesets so a fix to the tooltip doesn’t force a version bump on the table. When you do ship a breaking change, ship a codemod with it. If you rename a prop, the migration should be a script the consuming team runs, not a paragraph in a changelog nobody reads. Pair that with visual regression testing (Chromatic, or Playwright snapshots) so you catch the unintended pixel shifts before they become forty bug reports. The teams that trust your releases are the teams that upgrade, and upgrade rate is the real health metric for a design system.
Performance budgets, or how one date picker taxes every app
Every team that imports your package inherits its weight, so the shared library is exactly where bloat does the most damage. The classic failure is a barrel file: export * from './components' means importing a single Badge can drag the whole library into a bundle when tree-shaking gives up. Mark the package "sideEffects": false, ship real ES modules, and expose per-component entry points so a team pulling in Button never pays for the DatePicker they didn’t touch.
Put a hard number on it. A shared component should cost a few kilobytes gzipped, not tens. Wire a size check into CI (size-limit, or a bundle budget in the build) that fails the pull request when a component crosses its ceiling, and be wary of heavy dependencies. One rich-text editor or a moment-style date library living inside the shared package silently taxes every product that installs it. If a component genuinely needs something large, lazy-load it behind a dynamic import so the cost lands only where the feature is actually used.
The candidates who do well on this round treat the design system as a product whose users are other engineers, and those users can walk. They talk about adoption, migration cost, and the blast radius of a change with the same seriousness they would give an algorithm’s time complexity. The ones who struggle spend twenty minutes perfecting the button. The button was never the question.
Useful next steps:
