Dark mode and accessibility
How contrast rules apply in dark themes, why pure black and white can backfire, and how to keep colored text, logos and focus rings visible.
5 min read
Dark mode doesn't change the accessibility rules. Text still needs 4.5:1 under 1.4.3 Contrast (Minimum) (AA), or 3:1 when it's large, and interface components still need 3:1 under 1.4.11 Non-text Contrast (AA). What changes is where designs break. Colors picked for a white background are reused on near-black and quietly fail, logos with dark marks vanish, and the focus ring that looked fine in light mode disappears.
Many wallets, exchanges and dashboards ship a dark theme, often as the default, so these failures turn up everywhere. Each one has a straightforward fix.
Colors that pass in light mode can fail in dark mode
A color's contrast depends on the background, so every color in a light theme has to be checked again against the dark one. Some common examples, measured against white and against a #121212 dark background:
| Color | On white | On #121212 |
|---|---|---|
Red #dc2626 |
4.83:1 | 3.88:1 |
Blue #2563eb |
5.17:1 | 3.62:1 |
Gray #6b6b6b |
5.33:1 | 3.52:1 |
Lighter red #f87171 |
2.77:1 | 6.77:1 |
Lighter blue #60a5fa |
2.54:1 | 7.37:1 |
Saturated mid-tones that work as text on white fall below 4.5:1 on dark backgrounds, and the lighter tints that work on dark fail on white. In practice, each status color needs two values, one per theme. Price changes are the classic case: a red for "down" that passes in light mode can fail in dark mode.
Skip the extremes
White text on a pure black background gives the maximum ratio of 21:1. That sounds ideal, but some people, including some with astigmatism, find that bright text on pure black seems to glow or bleed into the background, an effect often called halation. Long passages can become tiring to read.
The common fix is to pull both ends in a little. Text in #e8e8e8 on #121212 measures 15.29:1, still far above the 7:1 that the AAA criterion 1.4.6 Contrast (Enhanced) asks for, and noticeably softer. Some people prefer maximum contrast, though, and operating systems let them ask for it. You can respond with the prefers-contrast media query:
@media (prefers-color-scheme: dark) and (prefers-contrast: more) {
:root {
--bg: #000000;
--text: #ffffff;
--text-muted: #d4d4d4;
}
}
Secondary text and raised surfaces
Muted text is a common failure point. Labels like "Balance", "24h volume" or "Network fee" are typically styled in a gray chosen to look quiet, and a gray that looks quiet on near-black is often too dim. #6b6b6b on #121212 measures 3.52:1 and fails for normal text, while #a3a3a3 measures 7.43:1.
Dark themes often show elevation with lighter surfaces instead of shadows, so cards, modals and menus sit on grays like #1f1f1f or #2a2a2a. Each step up reduces the contrast of everything placed on it. #a3a3a3 falls from 7.43:1 on #121212 to 5.69:1 on #2a2a2a. That's still a pass, but a gray that only just passed on the page background would fail on the card. Check text against the surface it actually sits on.
The same gray can be wrong for text and right for a border. #6b6b6b fails as body text on #121212, but at 3.52:1 it meets the 3:1 that 1.4.11 asks of an input's border when the border is what shows where the field is. Measure text and component boundaries against their own thresholds. The WCAG 2 formula also tends to overstate contrast on dark backgrounds; APCA vs WCAG 2 contrast shows by how much, and why secondary text that just reaches 4.5:1 can still look dim.
Logos on dark backgrounds
Many crypto logos have dark parts: black wordmarks, navy symbols, dark outlines. A black mark on a #121212 background measures 1.12:1, which means it's effectively invisible. Logos are exempt from 1.4.3 and 1.4.11, so there's no contrast number to meet, but people still need to recognize the token, wallet or exchange they're looking at.
You have three reasonable options:
- A single-color version in a light color, if the project has a mono version of its mark.
- A version on its own colored tile, where the brand's background color is part of the icon.
- A light tile behind the full-color logo, so it keeps its original colors.
What you shouldn't do is invert the logo with filter: invert(1). Inversion changes every color, not just the dark ones, so an orange mark turns blue and the brand becomes unrecognizable. The icon library here offers branded, mono and background styles for many logos, and shows logos that are too dark for its own dark interface on a light tile. When a dark logo needs a light tile goes into that decision in detail.
Focus indicators
A focus indicator shows a component's state, which 1.4.11 covers, so it needs 3:1 against the colors next to it. A dark blue ring that worked on white can fail on near-black: #1d4ed8 on #121212 measures 2.80:1. A light ring is the simplest dependable choice in a dark theme. #f5f5f5 on #121212 measures 17.18:1.
In token lists and icon grids, the ring sits next to logos of every color. Drawing it with an offset, so there's a strip of page background between the logo tile and the ring, keeps it readable whatever the logo looks like. Designing visible focus styles has more patterns.
Putting it together
Define colors as tokens once per theme, and set color-scheme so form controls, scrollbars and default backgrounds match:
:root {
color-scheme: light dark;
--bg: #ffffff;
--surface: #f5f5f5;
--text: #1a1a1a;
--text-muted: #595959;
--up: #15803d;
--down: #b91c1c;
--focus: #1a1a1a;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #121212;
--surface: #1f1f1f;
--text: #e8e8e8;
--text-muted: #a3a3a3;
--up: #4ade80;
--down: #f87171;
--focus: #f5f5f5;
}
}
:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
Every text token here passes 4.5:1 against both its page background and its surface color in its own theme. The lowest pairing is #15803d on #f5f5f5 at 4.60:1. If your product is dark-only, you can drop the light values, but keep the habit of checking each color against every surface it appears on. Up and down colors still need an arrow or sign next to them, since color alone can't carry direction.
Respect the system preference by default, and if you add a manual theme switch, remember the choice and make the switch itself reachable by keyboard, with a clear name such as "Dark theme".