DEV Community

Pixeliro
Pixeliro

Posted on

How to Switch Themes in CSS Without Breaking Your UI (Design Token Approach)

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Then define themes separately:

:root {
  --color-primary: blue;
}

.dark {
  --color-primary: red;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

1. Global Tokens

--blue-500: #3B82F6;
Enter fullscreen mode Exit fullscreen mode

2. Semantic Tokens

--color-primary: var(--blue-500);
Enter fullscreen mode Exit fullscreen mode

3. Components

.button {
  background: var(--color-primary);
}
Enter fullscreen mode Exit fullscreen mode

👉 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);
}
Enter fullscreen mode Exit fullscreen mode

Now you can use:

<button class="bg-primary text-white">
  Button
</button>
Enter fullscreen mode Exit fullscreen mode

Dark Mode Switching

:root.dark {
  --color-primary: red;
}
Enter fullscreen mode Exit fullscreen mode
document.documentElement.classList.toggle("dark");
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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:

👉 https://pixeliro.com/


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)