Back to System Design
System Design
hard
mid

What is component driven architecture and when would you use it?

Component-driven architecture builds UIs as compositions of small, independently developed, documented, and tested components. Build bottom-up: atoms → molecules → organisms → pages. Tools: Storybook for isolation + docs, design tokens for theming, a shared component library or design system. Benefits: reuse, consistency, parallel development, isolated testing. Costs: governance overhead, design-system maintenance, version coordination.

7 min read·~5 min to think through

What it is

Component-driven architecture (CDA) treats components as the primary unit of the UI: build them in isolation, document them, test them, and assemble the app from them. Top-down (page first, components extracted) vs bottom-up (components first, pages composed).

CDA is bottom-up by design.

The pyramid (atomic design)

ts
                    ┌──────────┐
                    │  Pages   │     <- routes, layouts
                    └──────────┘
                  ┌──────────────┐
                  │  Templates   │   <- layouts without data
                  └──────────────┘
              ┌──────────────────────┐
              │     Organisms        │ <- header, product card, sidebar
              └──────────────────────┘
          ┌──────────────────────────────┐
          │        Molecules             │ <- search box, form field
          └──────────────────────────────┘
      ┌──────────────────────────────────────┐
      │             Atoms                    │ <- button, input, icon
      └──────────────────────────────────────┘
                ┌────────────────┐
                │ Design tokens  │  <- color, space, type
                └────────────────┘

The terminology is from Brad Frost's Atomic Design. Variations exist (primitives / composed / patterns / pages); the layering principle is the point.

Why teams adopt it

  1. Reuse: Button defined once, used 200 times.
  2. Consistency: design language enforced at the leaf level.
  3. Parallel development: backend team can build a feature without waiting for design pages.
  4. Isolated testing: components testable without the whole app.
  5. Documented surface: Storybook + Docs makes the design vocabulary discoverable.
  6. Onboarding: new engineers learn the component library, not the whole app at once.

What makes a component 'good'

  • Single purpose: does one thing, has a clear name.
  • Predictable API: props are typed, sensible defaults, controlled or uncontrolled (pick one).
  • Composable: accepts children, slots, or render props where it makes sense.
  • Isolated: works in Storybook without app context.
  • Themed via tokens: no hardcoded colors / spacing.
  • Accessible: keyboard, screen reader, focus management.
  • Documented: usage example, do/don't, variants.

The tooling

  • Storybook / Ladle: render components in isolation, show variants, write docs.
  • Chromatic / Playwright visual tests: visual regression on every PR.
  • Design tokens: JSON / Style Dictionary for color, space, type, radii.
  • Headless primitives: Radix UI, React Aria, Headless UI provide behavior without styling.
  • Component test runner: Vitest + RTL or component-mode Playwright.
  • Bundle analyzer: ensure the library is tree-shakeable per component.

Architectural decisions

Headless vs styled

  • Headless (Radix, Headless UI): behavior + a11y, you bring styling. Maximum flex.
  • Styled (MUI, Chakra, Mantine): batteries-included. Faster to ship, harder to theme deeply.
  • Hybrid (shadcn/ui): copy code into your repo, customize freely.

Shared library vs per-app

  • Shared library: published to internal registry. Best for multi-app orgs. Needs versioning, governance.
  • Per-app: components live in the app repo. Simpler. Drift across apps.

Slot-based composition

jsx
<Card>
  <Card.Header>Title</Card.Header>
  <Card.Body>Content</Card.Body>
  <Card.Footer>Actions</Card.Footer>
</Card>

Better than prop bags for layouts. Mirrors HTML composition.

Anti-patterns

  • Component soup: 200 atom buttons that look almost the same.
  • Props explosion: a Card with 40 boolean props instead of 4 well-named variants.
  • Coupling to app state: a Button that knows about Redux defeats reuse.
  • No tokens: hardcoded colors everywhere, dark mode impossible.
  • Story-less components: undocumented, undiscoverable.
  • Visual drift: design system maintained by 1 person, the rest of the org bypasses it.

Versioning and governance

A shared component library is a contract. Treat it like one:

  • SemVer strictly. Breaking changes get a major.
  • Deprecation cycle, not silent removal.
  • Codemods for breaking migrations.
  • A design-system team or guild that owns the contract.

When it pays off

  • 3+ apps sharing UI.
  • Design system in active use across the org.
  • Team big enough to need parallel work streams.
  • Long-lived product where consistency matters.

When it's overkill

  • Single-page MVP being shipped in a week.
  • Team of 1-3 engineers.
  • Product still finding PMF — overhead exceeds benefit.

Modern context: RSC and CDA

Server components push you toward 'server primitives' vs 'client primitives.' The CDA layering still applies but the boundary shifts: leaf components stay simple, but you start distinguishing zero-JS server components from interactive client islands.

Mental model

CDA is the application of single-responsibility + composition to UI. Build leaf components in isolation, theme them with tokens, document them in Storybook, version them as a contract, and compose apps top-down. The win is consistency + reuse + parallel velocity; the cost is governance overhead. Match the investment to the org's actual scale.

Follow-up questions

  • How do you decide between a shared library and per-app components?
  • When does atomic design break down?
  • How do design tokens fit into a component library?
  • How does CDA interact with React Server Components?

Common mistakes

  • Hardcoded colors / spacing instead of tokens — theming impossible.
  • Component prop explosion — 40 booleans instead of variants.
  • Coupling components to app state — kills reuse.
  • No Storybook — components are invisible to the team.
  • Adopting a heavyweight design system for a 3-person team.

Performance considerations

  • Tree-shakeable per-component exports keep bundle size honest. Heavyweight design systems can add 100KB+ if imported wholesale — set up per-component imports. Storybook adds CI cost; cache builds.

Edge cases

  • A11y baked in vs left to consumers — pick one.
  • Controlled vs uncontrolled — don't mix in the same component.
  • Polymorphic 'as' prop — flexible but hard to type.
  • Server vs client component boundary inside the library.

Real-world examples

  • Material UI — comprehensive styled library.
  • Radix UI — headless primitives, behavior + a11y only.
  • shadcn/ui — copy-in, fully customizable.
  • Atlassian Design System, Polaris, Carbon — large org-scale libraries.

Senior engineer discussion

Seniors invest in CDA proportionally to org scale: tokens + headless primitives for a single team, full design system + governance for multi-app orgs. They guard against prop explosion and component soup, treat the library as a versioned contract, and use Storybook as the canonical documentation surface.

Related questions