Dark Mode Contrast: What Actually Passes on Dark Backgrounds
Switching a theme to dark does not make it accessible — it just moves the failures somewhere new. White headlines will pass with room to spare, but the muted grays and brand colors you tuned for white backgrounds behave completely differently on dark ones. Every number below is computed with the WCAG 2.1 formula against real dark surfaces, so you can copy the exact hex values.
The polarity flip: your light-theme grays reverse their verdicts
The WCAG formula is symmetric — it only compares two luminances — so flipping a pair from dark-on-light to light-on-dark changes the ratio. Two familiar grays tell the whole story:
| Gray | On white | On #121212 | Lesson |
|---|---|---|---|
| #9CA3AF | 2.54:1 fail | 7.38:1 AAA | A failing light-theme gray becomes an excellent dark-theme secondary. |
| #6B7280 | 4.83:1 AA | 3.88:1 fail | A passing light-theme gray fails in dark mode. Never copy a gray across themes unchecked. |
Rule of thumb: in dark mode, secondary text must be lighter than you expect. If your light theme's muted gray is around #6B7280, its dark-theme equivalent is closer to #9CA3AF or lighter.
Body and secondary text on #121212
#121212 is the standard near-black dark background (pure black is harsher and, on OLED screens, can smear during scrolling — near-black avoids both problems while still scoring AAA with white). Measured against it:
| Text on #121212 | Ratio | Verdict (normal text) |
|---|---|---|
| #FFFFFF (primary) | 18.73:1 | AAA pass |
| #E5E7EB (soft primary) | 15.13:1 | AAA pass |
| #9CA3AF (secondary) | 7.38:1 | AAA pass |
| #6B7280 (muted) | 3.88:1 | Fail (large text only) |
| #52525B (disabled look) | 2.42:1 | Fail (even large text) |
Note the disabled-text trap: #52525B at 2.42:1 fails even the 3:1 large-text bar. If disabled text still needs to be readable (a disabled button label, a caption), it is not exempt from contrast just because it is disabled — keep it at 3:1 or above, or remove it from the reading flow.
Cards are lighter than the background — re-check everything on them
Dark themes lift cards above the background (elevation). A card at #1E1E1E on a #121212 page is lighter, so every text color on it scores slightly lower:
| Text | On #121212 | On card #1E1E1E | Still passing? |
|---|---|---|---|
| #FFFFFF | 18.73:1 | 16.67:1 | Yes, AAA |
| #9CA3AF | 7.38:1 | 6.57:1 | Yes, AA (not AAA) |
| #6B7280 | 3.88:1 | 3.45:1 | No — large text only, either way |
The drop is small but it demotes #9CA3AF from AAA to AA on cards. If your design system promises AAA secondary text, use a lighter secondary (such as #D1D5DB, 11.31:1 on #1E1E1E) on elevated surfaces. And the same rule applies to other dark surfaces: on slate #0F172A, #9CA3AF scores 7.03:1 and #6B7280 scores 3.69:1 — the same pass/fail pattern, slightly different numbers. Always measure against the actual surface, not the page background.
Accent colors: bright brand colors love dark backgrounds
Here dark mode is generous. Bright accents that are unreadable on white become excellent text colors on dark:
| Accent on #121212 | Ratio | Same accent on white |
|---|---|---|
| #60A5FA blue | 7.37:1 AAA | 2.54:1 fail |
| #38BDF8 sky | 8.74:1 AAA | 2.14:1 fail |
| #A78BFA violet | 6.88:1 AA | 2.72:1 fail |
| #F472B6 pink | 7.07:1 AAA | 2.65:1 fail |
| #FBBF24 amber | 11.22:1 AAA | 1.67:1 fail |
| #34D399 emerald | 9.74:1 AAA | 1.92:1 fail |
So in dark mode you usually do not darken the brand color for text — use the bright version as-is. The dark-mode mistake is the mirror image of the light-mode one: darkening an accent "to be safe" can push it back toward failure. If you need a link color, test the bright accent first; it probably already passes. One caution: a passing ratio is not the whole story — run bright-on-dark pairs through the color blindness simulator, since hue-only distinctions (green success vs red error) still fail users who cannot tell the hues apart.
The opacity trap: measure the blended color, not the percentage
Many design systems define dark-mode text as white at an opacity — 87% for primary, 60% for secondary, 38% for disabled. That is fine only if you check what the blend actually produces on each surface:
| White opacity over #121212 | Blended solid | Ratio | Verdict |
|---|---|---|---|
| 87% | #E0E0E0 | 14.19:1 | AAA pass |
| 60% | #A0A0A0 | 7.16:1 | AAA pass |
| 38% | #6C6C6C | 3.57:1 | Fail (large text only) |
The 38% "disabled" level fails normal text — same trap as #52525B above. Worse, opacity blends depend on what is underneath: the same 60% white scores 7.16:1 over #121212 but 6.77:1 over a #1E1E1E card (blended #A5A5A5), and 38% white gives 3.57:1 on both — failing on both. Prefer solid grays from a fixed scale so a token means the same ratio everywhere it is used.
Dark mode checklist before you ship
- Re-measure every text token against its actual surface — page background, card, and modal are three different backgrounds.
- Secondary text at #9CA3AF or lighter on #121212; nothing darker than that for normal-size text.
- Disabled and placeholder text at 3:1 or above if it still needs to be read; otherwise remove it instead of dimming it into invisibility.
- Use bright accents as-is for dark-mode links and labels — do not darken them "to be safe".
- Replace opacity-based text colors with solid grays so the ratio does not drift with the surface underneath.
- Check focus indicators and borders too: a 1px border at #52525B on #121212 (2.42:1) is nearly invisible as a focus ring.
- Run the finished dark theme through the color blindness simulator — passing ratios do not fix hue-only meaning.
Verify your dark theme in 10 seconds
Enter your background and text colors — the free checker computes the exact WCAG ratio and grades it against AA and AAA, in either polarity.
Open the Contrast Checker