How to Switch Themes in CSS Without Breaking Your UI (Design Token Approach)
Introduction
One of the most frustrating problems in modern frontend development is:
- Switching between light/dark themes breaks UI
- Styles become inconsistent over time
- Components need constant refactoring
If you've ever struggled with theme switching, you're not alone.
In this article, I'll show you a simple but powerful approach using:
👉 CSS Variables
👉 Design Tokens
👉 Semantic Colors
That allows you to switch themes without touching your components.
The Core Idea
Never change your UI structure. Only change values.
This is the key principle.
The Wrong Way ❌
Many developers do this:
.button-primary {
background: blue;
}
.dark .button-primary {
background: red;
}
Problems:
- Hard to scale
- Too many overrides
- Components become theme-dependent
The Right Way ✅ (CSS Variables)
Instead, use CSS variables:
.button-primary {
background: var(--color-primary);
}
Then define themes separately:
:root {
--color-primary: blue;
}
.dark {
--color-primary: red;
}
Benefits:
- Components never change
- Theme logic is centralized
- Easy to extend
Why This Works
Your UI consists of:
- Structure (HTML / Components)
- Style (CSS values)
👉 Themes should only affect values, not structure.
The 3-Layer Design Token System
For real-world apps, use this structure:
1. Global Tokens
2. Semantic Tokens
3. Components
1. Global Tokens
--blue-500: #3B82F6;
2. Semantic Tokens
--color-primary: var(--blue-500);
3. Components
.button {
background: var(--color-primary);
}
👉 This makes your system scalable and maintainable.
Using Tailwind v4 (Modern Approach)
Tailwind v4 introduces @theme, which works perfectly with tokens:
@theme {
--color-primary: var(--brand-primary);
}
Now you can use:
<button class="bg-primary text-white">
Button
</button>
Dark Mode Switching
:root.dark {
--color-primary: red;
}
document.documentElement.classList.toggle("dark");
Real Example (Generated Theme)
I’ve been working on a tool that generates full semantic color systems:
👉 https://pixeliro.com/brand-palettes
👉 https://pixeliro.com/brand-color-palette/b8e3fd82-7cec-4307-997e-875c9e2108b5
It can:
- Generate semantic colors from a brand color
- Export Tailwind v4 theme
- Provide a full token system
Example:
@theme {
--color-primary: var(--brand-primary);
--color-surface-base: var(--surface-base);
--color-text-primary: var(--text-primary);
}
Common Mistakes
- Hardcoding colors (
#fff,#000) - Skipping semantic tokens
- Overriding styles per component
👉 These will break your system over time.
Best Practices
- Always use semantic tokens
- Never hardcode colors
- Keep theme logic at root level
- Separate structure and styling
Final Thoughts
If you follow this approach:
👉 Your UI will never break when switching themes
👉 Adding new themes becomes trivial
👉 Your design system becomes scalable
Try It Yourself
You can experiment with semantic color generation here:
Closing
If you're building a modern design system,
this approach will save you a lot of time and headaches.
Let me know how you're handling themes in your projects 👇
Top comments (0)