Dark mode is a second set of color tokens for the same interface, tuned so text stays legible and depth still reads on a dark surface. It is not the light theme with the colors flipped. Invert your palette blindly and you get grey text nobody can read, brand colors that vibrate, and shadows that vanish. I've shipped dark themes that took a day and dark themes that took three weeks, and the difference was always whether the color system was built for it from the start.
So where do most teams go wrong? They treat dark mode as a filter you apply at the end. It isn't. It's a parallel palette, and the interface has to be built on tokens for it to work at all.
TL;DR: Dark mode isn't the light theme inverted. It's a second set of color tokens tuned for a dark surface. Build components on semantic tokens, recheck every text pair against WCAG 4.5:1, and show depth by making higher surfaces lighter grey instead of casting shadows the way you would in light mode.
Why Is Dark Mode More Than Inverting Colors?
Flip #ffffff to #000000 and #111111 to #eeeeee, and on paper you have dark mode. Load it and the problems start. Pure white body text on pure black causes halation, that smearing glow that makes text feel like it's buzzing. Your carefully tuned brand blue now looks radioactive. The subtle grey shadow that separated a card from the background is gone, because you can't cast a darker shadow on an already-dark surface.
Dark mode changes the physics of the screen. In light mode, depth comes from shadows getting darker. In dark mode, depth comes from surfaces getting lighter. That single inversion rewrites how you show hierarchy, and no automatic filter understands it.
Why Should You Start With Tokens, Not Colors?
Here's the rule that saves you: components should reference semantic tokens like color/surface and color/text-primary, never raw hex values. Each token holds two values, one per theme. Switch the theme and every component updates, because none of them know the actual color, only the role.
If your product doesn't have this yet, that's the real first task. Our step-by-step design system guide walks through building the token layer, and the design tokens Figma to code guide shows how those tokens survive the handoff to engineering. Retrofitting dark mode onto hard-coded colors means hunting through sixty components by hand. Tokens turn it into one variable swap.
How Do You Get the Contrast Right?
This is where dark themes quietly fail. WCAG 2.2 still asks for 4.5:1 on normal text and 3:1 on large text, and dark surfaces make it easy to miss. That soft grey caption that passed on white can drop well below 4.5:1 on a #121212 surface. Every text-on-surface pair needs rechecking, not assuming.
Test each combination before you commit. Our free WCAG contrast checker gives you the live ratio with pass or fail badges as you tune the colors, which beats eyeballing it. Aim for AA on body text, and treat AAA as the target for anything read in long sessions. Contrast is also a hierarchy tool, not just a compliance box, the same way it works in the visual hierarchy principles guide.
How Do You Show Elevation With Light Instead of Shadow?
In light mode a modal floats above the page on a soft shadow. In dark mode that shadow disappears into the background. So you flip the model: the higher a surface sits, the lighter its grey. Your base might be #121212, a raised card #1e1e1e, a modal #242424, a menu #2c2c2c. Each step up in the stack gets a touch brighter, and the eye reads that as closer.
| Surface level | Example grey | Reads as |
|---|---|---|
| Base background | #121212 | Furthest back |
| Raised card | #1e1e1e | One step up |
| Modal | #242424 | Floating above |
| Menu | #2c2c2c | Closest to you |
This is exactly the pattern that keeps dashboard interfaces legible in the dark, where dozens of panels compete for attention and shadow alone can't separate them. Layer with light. Reserve shadow for a subtle assist, not the whole job.
What Common Mistakes Should You Skip?
A few traps I keep seeing, and how to avoid them:
- Pure black backgrounds. Start at #121212, not #000000. Black is harsh and gives you nowhere to go for elevation.
- Reusing light-mode accent colors. Desaturate and lighten brand colors for the dark theme, or they'll vibrate.
- The white flash. Set the background color before the CSS loads and honor
prefers-color-schemeat the root, so a user in dark mode never gets blinded by a white flash on load. - Forgetting images and illustrations. A white-background PNG looks like a hole punched in your dark UI. Use transparent assets or swap them per theme.
Dark mode done well feels effortless, and that's the trap: it looks simple, so teams underestimate it. Build it on tokens, check every contrast pair, and let light carry your elevation. Do that, and the second theme stops being a retrofit and starts being what it should have been all along, one interface that reads well whatever the lights are doing.