Why your light/dark mode keeps breaking — and how to fix it for good
If your light mode looks great and your dark mode looks broken (or vice-versa), you've probably met the most common front-end trap: a design that was built for one mode and patched into the other. The patches pile up, specificity wars break out, and every new component reintroduces the same bugs.
The fix isn't more overrides. It's a single source of truth for color, expressed as semantic tokens.
Hardcoded colors are the root cause
When a component says "this text is #1a1f3a," it can only ever be right in one mode. Flip the background to dark and that near-black text vanishes. Multiply this across hundreds of components and you get a system that needs a second stylesheet just to undo itself.
Semantic tokens invert the relationship. Components don't ask for a color — they ask for a role: background, foreground, muted-foreground, border, primary. Each role is defined once per theme as a contrast-checked pair. Switch the theme and every component updates, because none of them hardcoded anything.
Design tokens as contrast pairs
The trick is to design tokens in pairs and verify them against WCAG contrast ratios up front. Your muted text token, for example, should clear 4.5:1 against both its light and dark backgrounds. Do that work once, in one file, and accessibility stops being a per-component afterthought.
Gradients deserve special care: gradient text with a transparent fill looks slick on a dark hero and disappears on a white one. Reserve that effect for large display headings where you control the background, never for body copy.
The payoff
Once tokens drive everything, the second stylesheet disappears, the !important declarations disappear, and new components are correct in both modes the moment they're written. Theming becomes boring — which is exactly what you want.
Let's build something that lasts.
Tell us where you're headed. We'll bring the engineering, design, and delivery to get you there.
Prefer to talk? Call +91 771 470 0300
