Designing a Dark-Mode Colour Palette (the Right Way)
You flip a switch, invert the colours, and ship it. Two days later the bug reports arrive: the accent buttons glow like radioactive candy, the pure-black background gives people a headache, and the body text buzzes against it. Dark mode isn't your light theme turned inside out. It's a different room, lit differently, and it asks for its own palette.
I've built a few of these now, and the failures always trace back to the same shortcut. Someone treated dark as the absence of light. It isn't. Dark mode is a set of quiet, deliberate choices about how much light, where, and how saturated. Get them in order and the thing feels calm instead of loud.
Why "just invert everything" fails
Inversion is seductive because it's one line of code. It also breaks the moment real colour is involved.
Flip a light theme and your soft grey background becomes near-black, your dark navy text becomes pale, and your carefully chosen brand blue lands somewhere gaudy. The relationships you tuned in daylight don't survive the trip. Worse, inversion doesn't touch saturation, so a colour that felt grounded on white now screams on black. Human eyes react to bright, saturated colour on a dark field far more strongly than the same colour on a light one. That red you loved? On black it vibrates.
So throw out the shortcut. Build the dark palette as its own thing, from the surface up.
Start with a very dark grey, never pure black
Pure black — #000000 — sounds like the obvious background. Resist it.
Black is a wall. Text on it produces the harshest possible contrast, and that maximum contrast makes fine type shimmer and smear for a lot of readers, an effect called halation. It's fatiguing. Reach instead for a very dark grey with a whisper of your brand hue baked in. Something like #121316 or #16181d. The near-black still reads as "dark," but it gives your shadows room to exist and your surfaces room to separate.
Google's own Material dark guidance landed on #121212 for exactly this reason. Not dogma. Just a sensible floor.
Layer your surfaces by stepping lightness
Here's the part inversion can never give you. In a light UI, elevation reads through shadow — a card floats above the page because it casts a shadow. On a dark background, shadows mostly vanish. You can't see a dark shadow on a dark surface. So depth has to come from light.
The trick: as a surface rises toward the viewer, make it slightly lighter. Your base sits at the bottom. A card sits a step up. A popover or menu, higher still.
- Base — the darkest, roughly
#121316 - Raised surface — a card or panel, a notch lighter, say
#1c1e24 - Overlay — menus and dialogs, lighter again, around
#26282f
Three or four steps is plenty. Each one is a small, even bump in lightness, not a leap. Name these as roles rather than raw hex — surface, surface-raised, surface-overlay — and they become tokens you can reuse everywhere. The Design Tokens tool exists for exactly this: give each step a job, export the set, stop copy-pasting hex codes across files.
Tame your text and your accents
Two mistakes hide here, and both are about turning the volume down.
First, text. Pure white — #ffffff — on your dark grey is too much. It's the same halation problem in reverse, and it makes long reading tiring. Pull your primary text back to an off-white, around #e8e9ec, and let secondary text fall further, to a muted grey like #a1a4ad. You now have a hierarchy without a single harsh edge.
Second, accents. This is where most dark palettes go wrong. A saturated brand colour that looked confident on white will glare on black. The fix is two small moves: drop the saturation a little, and lift the lightness a little. A deep #1971c2 blue that anchored your light theme wants to become something airier — closer to #4dabf7 — so it reads as bright without burning. Same hue, calmer delivery.
Do the same for every semantic colour. Your success green, your warning amber, your error red — each one desaturated and lightened for the dark field, or they'll all fight for attention at once.
Let OKLCH do the heavy lifting
Building these lightness steps by hand in hex is guesswork. You nudge a value, squint, nudge again. OKLCH turns that into arithmetic.
Its first number is perceived lightness, on a scale from 0 to 1, and it means the same thing across every hue. So a dark scale becomes a list you can almost read aloud: oklch(0.16 0.02 265) for the base, oklch(0.22 0.02 265) for the raised surface, oklch(0.28 0.02 265) for the overlay. Same hue, same tiny chroma, lightness climbing in even steps. To desaturate an accent for dark mode, drop the chroma and raise the lightness — two numbers, no squinting. If you think in hex today, run a few of your colours through the Converter to see their OKLCH values and the logic clicks fast.
Check every text pair before you ship
None of this matters if people can't read it. Dark mode has a sneaky failure: mid-grey text on a mid-dark surface looks stylish and fails contrast badly. Your secondary text and your disabled states are the usual culprits.
So test the pairs. Run your primary text, your secondary text, and your accent-on-surface combinations through the Contrast Checker and make sure each clears the WCAG bar — 4.5:1 for body copy, 3:1 for large text. When a pair falls short, the checker hands you a tuned shade that passes. Tweak, retest, lock it in. Assemble and fine-tune the whole set in the Palette editor, where you can eyeball the surfaces and accents together before any of it reaches code.
Common questions
Can I reuse my light-mode brand colour as-is?
Usually not for accents. The hue can stay, but a light-theme accent is almost always too saturated and too dark for a dark field. Lift its lightness and cut its chroma until it reads bright but not blinding. Neutrals get rebuilt entirely.
How many surface levels do I actually need?
Three covers most interfaces: base, raised, overlay. Add a fourth only if you genuinely stack elements that deep. More than that and the steps get so close nobody can tell them apart — you've made work, not clarity.
Should the dark background carry a colour tint?
A faint one helps. A pure neutral grey can feel dead. Bleeding a few degrees of your brand hue into the dark greys — a hint of blue, a hint of warm — makes the whole thing feel designed rather than defaulted. Keep it subtle enough that nobody could name the colour.
Dark mode rewards restraint. Dim the black, soften the white, calm the accents, step the surfaces, and prove every pair readable — then it feels like a room someone actually lit. Open the Palette editor and build your dark set surface by surface.
Ready to try it? Every tool on Paleta runs free in your browser — no sign-up, nothing uploaded.
Explore the tools →