Skip to content
← Back to blog
Dark mode

Building a dark mode UI palette that does not glow like a neon sign

Dark theme is not “invert the light theme and call it a night.” Surfaces need depth, text needs restraint, and your brand accent might need a quieter cousin after sunset.

Flip a product to dark mode and three things break first: gray text that looked fine on white but vanishes on charcoal, borders that turn into laser lines, and a primary button that feels like it is shouting in a library. The fix is not “use #000000.” It is a small stack of surfaces with slightly different lightness, borders that whisper, and accents desaturated just enough to sit on deep backgrounds.

Start with surfaces, not the accent

Users spend most of their time on backgrounds — page, card, popover, sidebar. Give each layer a named step: base, elevated, overlay. In practice that often means three neutrals a few points apart, not three unrelated grays pulled from a mood board. If you only adjust one knob, adjust lightness before hue. A faint blue or violet in your dark neutrals can feel cohesive; a random green-gray usually reads as “bug.”

Rule of thumb

Reserve your brightest accent for primary actions. Everything else — icons, links, tags — can sit one chroma step down so the UI has one clear “loudest” element per screen.

Text: three weights of gray, not fifty

Primary body copy wants high contrast against the base surface. Secondary labels and timestamps can drop a step. Placeholder and disabled text drop again — but should still be legible if someone screenshots the form for support. Run those pairs through the contrast checker; WCAG 4.5:1 for normal text is still the bar that keeps you out of trouble in audits.

Borders and dividers in low light

On light UI, a hairline border might be #e5e7eb. On dark UI, the same trick often fails — pure light lines vibrate. Prefer borders that are only slightly lighter than the surface they sit on, or skip borders entirely and use elevation (shadow or background step) to separate regions. Modals and sticky headers are where elevation matters most; dense tables sometimes still want a divider — just not white at 20% opacity stretched across the whole grid.

Accents and semantic colors at night

Brand purple that pops on white can feel radioactive on #0f172a. Try lowering chroma or lightness for dark-specific token slots: --brand-primary-dark mapped in your [data-theme='dark'] block. Success, warning, and error hues benefit from the same treatment — green that reads “success” in light mode can look like terminal phosphor if you paste it unchanged onto a near-black card.

Generate once, theme twice

Build light and dark ramps from the same anchor in the CSS variables generator (OKLCH mode helps keep steps even across hues). Scope overrides on html or data-theme the way ChromaXP does site-wide. Test a real screen — settings page, data table, empty state — not just the marketing hero. Dark mode quality shows up in the boring views people use daily.