ADR 003: CSS Custom Property Theming Engine
Status: Accepted
Date: 2026-05-23
Context
Synapse UI must support runtime theme switching (Light, Dark, High-Contrast) without requiring consumers to recompile their application or maintain separate build targets. Enterprise apps often need instant theme toggling and brand customization.
We evaluated several theming approaches for an Angular component library.
Decision
Implement theming via CSS custom properties (CSS variables) managed by a ThemeService that injects token maps onto document.body at runtime.
Token naming convention: --synapse-{category}-{name}.
Three built-in themes ship with the library. Consumers can override individual tokens via ThemeService.setCustomTokens().
Consequences
Positive
- Zero JavaScript per component for theme awareness — components just reference
var(--synapse-*). - Instant theme switching with no page reload.
- Consumers can override tokens in CSS without forking the library.
- Works with Shadow DOM if tokens are inherited (documented limitation for encapsulated components).
- High-contrast theme is straightforward to implement as an alternate token map.
Negative
- CSS custom properties have no compile-time validation — typos in token names fail silently.
- Shadow DOM encapsulation requires explicit token forwarding or
:hostdeclarations. - No type safety in CSS itself (mitigated by TypeScript token maps in
@synapse-ui/theme).
Alternatives Considered
| Alternative | Why rejected |
|---|---|
| SCSS variables per theme | Requires separate builds per theme; no runtime switching |
| Angular Material theming | Couples library to Material Design; heavy dependency |
| CSS-in-JS (e.g., Emotion) | Not idiomatic in Angular; adds runtime overhead |
| Tailwind CSS plugin | Forces Tailwind on all consumers; conflicts with token approach |
CSS @media (prefers-color-scheme) only | Cannot support manual toggle or high-contrast mode |