Back to React
React
easy
mid

Should you support per component theming or global theming, and how do you decide?

Global theming as the foundation (design tokens via CSS custom properties on the root), with scoped overrides where genuinely needed — a subtree can redefine tokens locally. Per-component theming should be the exception, layered on the global system, not a parallel system.

6 min read·~12 min to think through

It's not either/or — it's global as the foundation, scoped overrides as the exception. The mechanism that makes both work cleanly is CSS custom properties (design tokens).

Global theming — the base layer

Define your design tokens once on the root:

css
:root {
  --color-bg: #fff;
  --color-text: #111;
  --color-primary: #2563eb;
  --space-md: 16px;
}
[data-theme="dark"] { --color-bg: #111; --color-text: #eee; }

Every component consumes tokens (background: var(--color-bg)). Switching the theme flips a root attribute — the whole app updates with no re-renders.

Scoped / per-component theming — layered on top

Because custom properties cascade and can be redefined on any element, a subtree can override tokens locally:

css
.promo-section {
  --color-bg: #1a1a2e;   /* this section is always dark */
  --color-primary: gold;
}

Everything inside .promo-section automatically re-themes — no prop drilling, no separate theme system. Same for a "danger" zone, a marketing banner, an embedded widget, or a component that ships its own theme prop.

When you actually need per-component theming

  • A always-dark section inside a light app (or vice versa).
  • A reusable component library where consumers pass a theme/variant.
  • White-label / multi-tenant: a subtree branded per tenant.
  • A/B-tested or campaign-specific UI.

Why not make everything per-component?

  • Consistency — a global system is what enforces a coherent look. Per-component theming everywhere = drift and chaos.
  • Maintainability — one place to change the brand color, not 200.
  • Performance — token-based theming is one attribute flip; scattered JS theming per component causes re-renders.

Implementation note

CSS custom properties handle ~all of this natively. CSS-in-JS ThemeProviders (styled-components, Emotion) can nest for scoped themes too, but they re-render React subtrees on theme change — custom properties don't. Prefer tokens; use a ThemeProvider mainly to expose tokens to JS if needed.

Follow-up questions

  • How do CSS custom properties make scoped theming work without prop drilling?
  • Why is token-based theming more performant than CSS-in-JS ThemeProvider nesting?
  • How would you handle multi-tenant white-label theming?
  • What goes wrong if every component manages its own theme?

Common mistakes

  • Building per-component theming as a parallel system instead of overrides on the global one.
  • Making everything per-component, losing consistency and a single source of truth.
  • Using JS-driven theming that re-renders subtrees on every theme change.
  • Hardcoding colors instead of consuming tokens.

Performance considerations

  • CSS custom property theming is a single root (or subtree) attribute change with no React re-renders. Nested CSS-in-JS ThemeProviders re-render their subtrees on theme change. Tokens scale to any number of components for free.

Edge cases

  • An always-dark widget inside a light app.
  • Multi-tenant apps needing per-tenant branding on a subtree.
  • Third-party components that don't consume your tokens.
  • Nested scoped overrides interacting unexpectedly.

Real-world examples

  • A design system shipping tokens as CSS variables; product apps override a few per surface.
  • Multi-tenant SaaS re-theming a tenant's subtree by setting tokens on a wrapper.

Senior engineer discussion

Seniors refuse the false binary: global token system as the backbone (consistency, one source of truth, cheap switching), with scoped overrides via cascading custom properties for the genuine exceptions. They contrast token-based theming's zero-rerender cost with CSS-in-JS ThemeProvider re-renders, and cite multi-tenant/white-label as the real per-component use case.

Related questions