How to Ship CSS Themes Without Specificity Wars
Theme systems collapse when every new style requires a stronger selector. It feels like patching leaks in a pipe. The fix is to move from selector power to token ownership.
1) Expose design tokens as CSS variables
:root {
--bg: #0e1116;
--text: #f4f6f8;
--accent: #20b2aa;
}
2) Scope themes by attribute, not nested selectors
[data-theme="light"] {
--bg: #ffffff;
--text: #111827;
--accent: #0ea5e9;
}
3) Keep components consuming tokens only
.card {
background: var(--bg);
color: var(--text);
border-color: color-mix(in srgb, var(--accent) 30%, transparent);
}
Failure pattern
- Component files redefining color constants directly.
- Theme overrides chained with long selector paths.
- No visual regression checks when theme tokens change.
What to verify
- Component CSS has no hard-coded theme colors.
- Switching theme flips tokens, not selector precedence.
- Theme snapshots pass for key layouts.