Would you support per-component theming or global theming
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.
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:
: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:
.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.