Dark mode is everywhere in apps and websites, but a dark background alone does not make an interface accessible. If text, links, and buttons sit on colors with too little contrast, users with low vision or glare struggle—and you risk failing WCAG audits. This guide explains how to build an accessible dark mode color palette using clear roles (canvas, surface, text, accent), with hex swatches next to each idea as you read—not only at the end—and how to verify everything with a contrast checker and palette generator.
Starter stack you can copy into the contrast checker:
Canvas
Surface
Border
Text
Accent
Why dark mode still needs contrast discipline
WCAG applies to foreground vs background, not to “light theme” vs “dark theme.” On dark UIs, designers often pick mid-gray body text or muted purple links that look fine on a calibrated monitor but fall below 4.5:1 for normal text. Muted borders and placeholder text are other common failures.
Your goal is simple: every piece of readable content and every primary control must meet at least WCAG AA (4.5:1 for normal text, 3:1 for large text and some UI components). Use an accessibility contrast checker on real pairs—e.g. body text on surface, label on canvas, button label on button fill—not on abstract “brand colors” alone.
On a typical dark surface, mid-gray text often fails for body copy; lighter neutrals usually pass—compare in your checker:
Surface
Risky body (test)
Safer body text
Remember that user settings matter: system “increase contrast” modes, color filters, and outdoor glare all make marginal ratios feel worse. Building in a little headroom above the minimum—especially for secondary text and disabled states—reduces support tickets and makes the product feel more polished without sacrificing the dark aesthetic.
Common dark mode mistakes (and how to avoid them)
Teams often copy light-theme brand colors into dark layouts without retesting. A vibrant blue that passed on white may fail as small link text on charcoal. Another pitfall is overusing desaturation: grayed-out body copy looks sophisticated until it drops under 4.5:1. Decorative gradients behind text can also hide failing contrast if you only sample a single pixel.
Treat disabled controls carefully: WCAG still expects perceivable boundaries and, where possible, information not conveyed by color alone. If you must lower contrast for disabled buttons, pair it with clearer labels or layout so users are not stuck guessing. Finally, do not assume “inverted” light theme hex values will work; dark UI layering (canvas vs surface) changes every relationship, so test the dark theme as its own system.
Typical “copy from light mode” failures—always re-check on your real dark surface:
Dark surface
Light-theme blue link
Pastel link (risky)
Soft purple link
Safer light link
Structure your palette by role
Treat dark mode like a small design system. Typical roles:
- Canvas — full page background (often the darkest layer).
- Surface — cards, modals, inputs (slightly lighter than canvas for depth).
- Border / divider — subtle separation without harsh lines.
- Text primary & secondary — body and supporting copy.
- Accent — links, primary buttons, focus rings.
Limit yourself to roughly five to seven core UI colors plus semantic colors (success, error) if you need them. More colors mean more combinations to test. A color palette generator helps you extend a single accent into harmonious variants for hover and pressed states—then you still verify each state in the contrast checker.
Neutral starter (dashboards & marketing): near-black canvas, lifted surfaces, light gray text, and an indigo accent for actions—mapped to the roles above:
- #0a0a0a — canvas
- #171717 — surface
- #3f3f46 — border
- #e4e4e7 — primary text
- #a1a1aa — secondary text
- #818cf8 — accent / links
Canvas
Surface
Border
Text
Muted
Accent
Check #e4e4e7 on #171717, and #ffffff or near-white on #818cf8 for filled buttons. Adjust accent saturation if the button pair fails.
Layer depth—slightly lighter “elevated” surfaces for modals and dropdowns:
Canvas
Card
Popover
Hairline
Cool blue-gray (product / dev tools)
Slightly blue-tinted neutrals read “technical” and work well for SaaS and developer products.
- #020617 — canvas
- #0f172a — surface
- #334155 — border
- #f1f5f9 — primary text
- #94a3b8 — secondary text
- #38bdf8 — accent
Canvas
Surface
Border
Text
Muted
Accent
Interactive and semantic accents on the same family—test each on #0f172a or your surface:
Hover accent
Pressed accent
Success
Error
Warning
Sky accents on very dark blue can fail for small link text; use the contrast checker and, if needed, a slightly lighter accent or bolder link weight. Secondary text at #94a3b8 is often acceptable for non-body copy—confirm on your exact surface hex.
Warm dark (reading & lifestyle)
Warm near-blacks feel less clinical. Use a restrained warm accent so the UI does not compete with imagery.
- #0c0a09 — canvas
- #1c1917 — surface
- #44403c — border
- #fafaf9 — primary text
- #a8a29e — secondary text
- #fbbf24 — accent
Canvas
Surface
Border
Text
Muted
Accent
Warm-tinted success and calm secondary accents:
Success soft
Success strong
Amber alt CTA
Input stroke
Yellow and amber accents frequently fail on dark backgrounds for small text. Prefer the accent for buttons with dark labels, or use a softer amber for large headings only—always confirm in your contrast checker.
Aligning dark mode with your light theme
Users switch themes frequently; brand recognition should stay consistent even when luminance flips. Keep the same accent hue family where contrast allows, or define a slightly lighter or more saturated accent specifically for dark surfaces. Neutrals can shift from warm paper in light mode to warm stone in dark mode—as in the warm dark palette above—so the brand still feels cohesive.
Document tokens (or a simple table) with names like color.text.primary mapped to different hex values per theme. That way engineers and designers do not hard-code one-off grays. When you add a new component, you assign semantic tokens instead of guessing hex codes, and you only run contrast checks on the token pairs you actually ship.
Light vs dark token pairs (same role, different hex)—verify both columns:
Light page
Light body
Dark canvas
Dark body
Light link
Dark link (test)
Buttons, links, and focus on dark backgrounds
Treat filled buttons, outline buttons, and text links as separate pairs. Ghost buttons need visible borders. Focus indicators must be distinguishable from both canvas and surface—often a light ring on dark or a double ring. If you use transparency, flatten the effective color in your design tool and test that value, because opacity changes contrast.
Example pairs to verify: accent fill with a light label, plus a bright ring that reads on both canvas and surface.
Accent fill
Label on accent
Focus ring
Outline and ghost buttons on charcoal—stroke and label both need to read:
Canvas
Outline stroke
Outline label
Ghost border
Next steps
Pick one of the examples above or start from your brand accent in the palette generator, then define canvas and surface neutrals. Document hex codes for light and dark themes. Run every text, link, and button combination through the contrast checker before release. For more on ratios and WCAG labels, see our WCAG contrast guide.
Frequently Asked Questions
Does WCAG apply to dark mode?
Yes. Contrast requirements apply to text and UI on dark backgrounds the same as on light ones. Aim for at least 4.5:1 for normal text and 3:1 for large text (WCAG AA).
What colors should a dark mode palette include?
Canvas, surfaces, borders, primary and secondary text, and accent colors for links and buttons—plus semantic colors if needed. Keep the set small and test every pair.
Should dark mode use pure black?
Not required. Many products use near-black to reduce strain and separate layers. Contrast between content and surfaces matters more than using #000000.
How do I test dark mode contrast?
Use a contrast checker with your exact hex values for each theme, including hover and focus states, on real background layers.
Try our free tools
Click above to use our tools — no account required
Share this article
Ad Space (728x90)