Building Dual Colour Schemes That Actually Work Together
Learn how to design light and dark themes that feel intentional, not like an aft…
Organize all your theme colours using CSS variables. We’ll show you the structure that scales well and makes theme switching simple — even when you add new colours later.
Building a dark mode isn’t just about inverting colours. You’re creating two complete visual systems that need to work together. Without a solid foundation, you’ll end up with scattered hex values throughout your codebase, making updates painful and mistakes inevitable.
CSS Custom Properties — also called CSS Variables — let you store colour values in one place. Change a single variable, and every component that uses it updates automatically. It’s not revolutionary, but it’s genuinely practical.
The core idea: Define colours once at the root level. Reference them everywhere else. When you switch themes, only the root values change.
The key is organization. You don’t want 50 variables called –color-1, –color-2, –color-dark-variant-light. That’s chaos. Instead, think about function — what’s each colour doing?
We recommend grouping by semantic purpose: backgrounds, text, borders, accents. Each group should be predictable. When a designer says “make the text lighter,” you should know exactly which variable to adjust.
Here’s what a solid structure looks like: background primaries (main page bg, secondary sections, cards), text layers (primary text, secondary text, muted text), and accent colours for interaction states. That’s roughly 12-15 variables for most sites.
Your `:root` selector is where you declare the default (usually light mode) values. Then you create a `[data-theme=”dark”]` selector that redefines those same variables with dark mode values.
Components reference the variables, never the hex values directly. A button doesn’t know if it’s in light or dark mode. It just uses `var(–color-primary)`, and that variable’s value changes when the theme changes. No component rewrites needed.
The real advantage appears when you’re three months in and someone says “the accent blue doesn’t work in dark mode.” You adjust one variable in your `:root` for dark theme. Everything updates instantly. No hunting through 50 CSS files.
As your site grows, your variable set will too. New colours for new features, variants for edge cases. The system only stays manageable if you’re disciplined about naming.
Don’t create variables on the fly. Before you add `–color-button-hover-focus-light-disabled`, pause. Can you use an existing variable? Does that state actually need its own colour, or are you overthinking it?
A well-designed system with 15 core variables handles 95% of your needs. The remaining 5% — very specific component states — those can have local overrides. But they’re exceptions, not the norm.
Enough for most websites
Light and dark mode
Changes propagate everywhere
Some developers still hardcode colours. `background: #f3f4f6;` instead of `background: var(–color-bg-secondary);`. When you change themes, that component doesn’t update. Don’t do this.
Someone calls it `–primary-color`, someone else uses `–accent`. Six months later, you’ve got 40 variables and nobody remembers which one does what. Establish a naming convention on day one.
You build light mode, switch to dark, and suddenly text is unreadable. That variable pair wasn’t thought through. Test every component in both themes before considering it done.
Text colour should contrast with its background. If you change background values but forget to adjust text colours, you’ll have invisible text. Variables are a system — treat them as one.
After managing theme systems across dozens of projects, we’ve learned what makes them sustainable. Here’s what matters.
Keep a living document of what each variable does. `–color-text-primary` is the main text colour. `–color-text-secondary` is for supporting text like descriptions. New team members should understand your system in 15 minutes.
Names should describe function, not appearance. Don’t call something `–color-light-gray`. Call it `–color-bg-tertiary`. The “light” and “gray” parts change depending on theme — the function stays the same.
Use tools like WebAIM’s contrast checker. Run through every text/background pair. Document which combinations pass WCAG AA. You’ll catch problems before users do.
You don’t need 50 variables on day one. Start with essentials: background layers, text hierarchy, accent colour, state colours (hover, active, disabled). Add more only when you genuinely need them.
CSS Custom Properties aren’t magic. They’re just a sensible way to organize colour information. But that simplicity is powerful. It’s the difference between spending two hours updating a colour scheme and two minutes.
You’re building systems that’ll live for years. Teams will modify them, expand them, troubleshoot them. A well-structured variable system makes all of that easier. It’s an investment that pays dividends.
Start small. Document clearly. Test both themes. And you’ll have a foundation that scales.
This article provides educational information about CSS custom properties and theme management techniques. It’s designed to help you understand best practices for organizing colour systems in web design. Every project has unique requirements — what works for one site might need adjustment for another. We recommend testing approaches thoroughly in your own environment before deploying to production. Browser support and specific implementation details should always be verified against current documentation.