Layer tokens, primitives, and composites
Stable design decisions flow into accessible behavior primitives and then opinionated product components without circular ownership.
FRONTEND SYSTEM DESIGN
Design a component library and its distribution/versioning strategy for dozens of consuming teams.

THE INTERVIEW SEQUENCE
Clarify scope
Set requirements
Draw data flow
Defend trade-offs
Start with the smallest coherent design. After the main path works, introduce failure, scale, accessibility, observability, and release strategy one constraint at a time.
Dozens of product teams consume React components on independent release schedules.
The library supports themes, internationalization, server rendering, and evergreen browsers with a declared support policy.
Accessibility conformance, typed APIs, and predictable upgrades are release gates rather than documentation promises.
Packages must remain tree-shakeable and avoid forcing duplicate framework or styling runtimes.
Stable design decisions flow into accessible behavior primitives and then opinionated product components without circular ownership.
Peer dependencies, exports, CSS ownership, and side-effect metadata protect bundle size and runtime compatibility.
Semantic versioning, deprecations, migration guides, and codemods make distributed adoption operationally possible.
Interaction, accessibility, visual, type, SSR, and browser tests catch different classes of regression.
Controlled and uncontrolled state, semantic callbacks, and stable slots support product needs without leaking internals.
type DialogProps = { open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; title: React.ReactNode; description?: React.ReactNode; children: React.ReactNode; };// Focus, Escape, labelling, and portal behavior remain library-owned.Impact: Bundles grow or context breaks across copies.
Mitigation: Declare peers and export maps correctly, test consumer fixtures, and monitor installed dependency graphs.
Impact: An upgrade silently changes product UI.
Mitigation: Scope layers, define injection order, test SSR/hydration, and avoid depending on consumer import accidents.
Impact: Keyboard or assistive-technology users lose a workflow.
Mitigation: Combine automated checks with interaction tests and manual AT coverage for high-risk primitives.
Impact: Teams stay pinned and the ecosystem fragments.
Mitigation: Canary real consumers, publish migrations and codemods, maintain a deprecation window, and track version adoption.
Drive the discussion yourself first. This is one strong answer delivered the way you would say it in the room — a model, not the only correct design.
This one is a distribution and versioning problem more than a components problem. The components are the easy part; the hard part is that dozens of teams consume them on their own schedules, so every decision is about not breaking those teams. Frame it around the monorepo, semantic versioning, and design tokens.
Step 1 · Clarify
Ask: 'How many consuming teams and applications? One framework or several? Do they all upgrade together or independently? And is there a design system with tokens already, or are we defining that too?' Assumptions: 'Dozens of teams, primarily one framework, teams upgrade on their own schedule, and we own the tokens as well.' Independent upgrades is the constraint that shapes everything.
Step 2 · Requirements
User-facing (the consuming developers): components that are accessible by default, themeable, and documented, that they can adopt without a big-bang migration. Hidden: a change must be shippable without forcing every team to update the same day, the bundle impact must be predictable, and breaking changes must come with a migration path.
Step 3 · Monorepo with three things in it
'One repository holds the component packages, a shared design-token package (colors, spacing, type as data, not hard-coded in each component), and a documentation and playground site built from the same source. Keeping them together means the docs cannot drift from the code and the tokens are the single place visual decisions live.'
Step 4 · Headless-first components
'Where it makes sense, components are built on composable headless primitives, so the behavior — keyboard handling, focus management, accessible roles — is separate from the visual styling. That makes them easier to theme and means the accessibility work is done once in the primitive, not re-litigated per skin.'
Step 5 · Versioning and release discipline
'Semantic versioning with automated changelog generation. Every change ships with a changeset declaring whether it is a patch, minor, or breaking. CI runs visual regression, unit, and accessibility tests against every component and gates the release. A breaking change is deprecated first with console warnings, ships with a migration guide and ideally a codemod, gets canaried against a few consumers, and only then goes out as a major after a declared window.'
Step 6 · How teams adopt
'Consuming teams update their dependency when they choose, guided by the changelog and by deprecation warnings that appear well before a breaking release. Nobody is forced to move on the library's schedule. The library owns interaction mechanics and controlled-state support; product data and business logic stay with the consumer.'
Step 7 · Bundle and theming
'Packages use ES module exports with accurate side-effect metadata and narrow entry points so consumers only ship what they import. Theming is semantic tokens — a component asks for surface and text, not a specific hex — so a new theme is a token file, not a component rewrite.'
How to close
'So: dozens of teams, independent upgrades, we own the tokens. A monorepo holds components, a token package, and docs from one source. Components are headless-first so behavior and accessibility are decoupled from styling. Semantic versioning with changesets, CI-gated visual and accessibility tests, and deprecate-migrate-canary-then-major for breaking changes. Teams adopt on their own schedule. The health signal I would watch: adoption rate and successful-upgrade rate, plus version fragmentation across consumers.'
The library owns interaction mechanics and supports controlled state; product data and business workflows remain with the consumer.
DESIGN CHECKLIST
Use this checklist to rehearse the case without reading the worked answer above.
Do not design yet. Use these questions to make hidden assumptions explicit and prevent solving the wrong problem.
How many consuming applications/teams, and do they share a build system?
Is theming/white-labeling required across consumers?
What's the tolerance for breaking changes — one release train, or independent versioning per consumer?
Is accessibility compliance a hard organizational requirement?
These are the user-visible capabilities the design must support. They define the first version's scope.
Provide a versioned, installable package (or set of packages) of shared UI components.
Support theming so different consuming products can apply their own visual identity on top of shared behavior.
Document each component's API, accessibility behavior, and usage guidance discoverably.
Allow consumers to adopt updates incrementally rather than being forced onto every release immediately.
These qualities shape the architecture even though users do not click them directly: latency, resilience, accessibility, consistency, and scale.
Backward compatibility guarantees clear enough that consuming teams can trust semantic versioning.
Consistent accessibility behavior (keyboard, screen reader) baked into components once, not re-implemented per consumer.
Reasonable bundle-size impact per component so adopting the library doesn't bloat every consumer's bundle.
Now assign responsibilities to clear boundaries. Each part should have one reason to change and an explicit contract with the next part.
A monorepo housing the component packages, a shared design-token package (colors, spacing, typography as data, not hardcoded per component), and a documentation/playground site built from the same source.
Components built with composable, headless-first primitives where feasible, so behavior (accessibility, keyboard handling) is decoupled from visual styling and easier to theme.
A visual regression and accessibility test suite run in CI against every component, gating releases.
Semantic versioning with automated changelog generation and a documented deprecation policy for breaking changes.
Trace one important user action from input to rendered result. This exposes ownership, race conditions, retries, and stale-data paths.
A component change is proposed, reviewed against the documented API and accessibility checklist, and merged with an accompanying changeset describing the semver impact.
CI runs visual regression, unit, and accessibility tests, then publishes a new version and updates the docs site on merge.
Consuming teams update their dependency on their own schedule, guided by the changelog and deprecation warnings surfaced ahead of breaking releases.
There is no perfect architecture. State what each choice optimizes, what it costs, and the signal that would make you revisit it.
Headless/composable primitives maximize flexibility for consumers but require more integration work per consumer than a fully pre-styled, opinionated component; the right balance depends on how visually different consumers actually need to be.
Strict semver discipline builds trust but slows down how quickly the library can evolve; a clear deprecation window is usually a better tradeoff than either extreme.
A single monorepo for all components simplifies cross-component consistency but couples release cadence; independently versioned packages decouple releases at the cost of more coordination overhead for shared tokens.
Scale the measured bottleneck rather than every box. Frontend scaling includes payload size, main-thread time, rendering work, cacheability, and release safety.
Publish components as independently importable modules (not one giant bundle) so consumers only pay for what they use via tree-shaking.
Automate visual regression testing so reviewing dozens of components' worth of changes doesn't require exhaustive manual QA on every release.
Provide codemods for breaking API changes so large consumers can adopt them mechanically instead of manually rewriting every call site.
Interviewers often probe these failure modes. Name them before being prompted and explain the guardrail you would add.
Baking a specific product's visual identity directly into 'shared' components instead of exposing it through theming/tokens, making the library unusable for other consumers.
Treating accessibility as a per-consumer concern instead of solving it once in the shared components, leading to inconsistent (and often broken) behavior downstream.
Shipping breaking changes without a deprecation window or migration tooling, which erodes trust and causes teams to pin to old versions indefinitely.
Your closing summary
Restate the critical user flow, the most important quality target, the architecture boundary that protects it, and the trade-off you accepted. Then name the first metric you would watch after launch.
Back to all cases →