Skip to the color picker

Dark mode colors: how to pick backgrounds, text and accents

Use a dark gray background such as #121212 instead of pure black, off-white text such as #e5e5e5 instead of pure white, and lighter, less saturated versions of your brand colors. Check text with APCA as well as WCAG 2, because WCAG 2 overrates contrast on dark backgrounds.

6 min read

Why pure white on pure black is tiring

White #ffffff on black #000000 is the maximum contrast a screen can show: 21:1 in WCAG 2 and Lc -107.9 in APCA. For a button label that is fine. For paragraphs it is too much.

The problem is halation. Bright letters on a black field seem to bleed light into the dark around them, so strokes look thicker and blurry, especially for people with astigmatism. Your eyes also keep adapting between the glaring text and the black gaps, which is tiring over a long read.

Lowering both ends fixes it without hurting readability. #e5e5e5 on #121212 still gives 14.87:1 and Lc -90.4, which is above APCA’s preferred level of Lc 90 for body text.

Best background colors for dark mode

Most dark themes use a background between OKLCH lightness 0.14 and 0.28. The table shows common choices with two text colors on top: #e5e5e5 for comfortable body text and #fafafa for maximum emphasis.

SurfaceOKLCH LKnown as#e5e5e5 text#fafafa text
#0000000Pure black16.67:1, Lc -91.020.12:1, Lc -104.6
#0a0a0a0.145Tailwind v3 neutral-95015.72:1, Lc -90.918.97:1, Lc -104.4
#1212120.182Material Design dark surface14.87:1, Lc -90.417.95:1, Lc -104.0
#18181b0.210Tailwind v3 zinc-90014.06:1, Lc -89.916.97:1, Lc -103.4
#1f29370.278Tailwind v3 gray-80011.65:1, Lc -87.514.06:1, Lc -101.0

Going from pure black to #18181b costs almost nothing in APCA terms: #e5e5e5 text drops from Lc -91.0 to -89.9. What you gain is room below the background for shadows and room above it for raised surfaces. #1f2937 is slightly blue (chroma 0.03), which works if your brand is cool and looks off next to warm accents.

WCAG 2 overrates contrast on dark backgrounds

The WCAG 2 formula treats a ratio the same whether the colors are light or dark. Human vision does not: differences between dark colors are harder to see. APCA models this, and the gap is large. The table shows which gray you need on each background to reach WCAG AA and two APCA levels.

BackgroundLightest gray at 4.5:1APCA of that grayGray for Lc 60Gray for Lc 75
#ffffff#767676Lc 71.6#8e8e8e, 3.28:1#6e6e6e, 5.10:1
#121212#7d7d7dLc -32.8#b2b2b2, 8.84:1#cccccc, 11.67:1
#000000#757575Lc -29.6#b1b1b1, 9.79:1#cbcbcb, 12.94:1

On white, a gray that just passes AA already reaches Lc 71.6. On #121212, a gray that just passes AA reaches only Lc -32.8, below APCA’s Lc 45 minimum for large headlines. To get the same APCA body text level of Lc 75 you need 5.10:1 on white but 11.67:1 on #121212.

The same happens with color. The blue #4a83f0 on #121212 passes WCAG AA with 5.17:1, yet its APCA value is Lc -37.6. In dark mode, treat 4.5:1 as a floor. For body text aim for about 9:1 or more, which is where a gray on #121212 reaches Lc 60, or check APCA directly.

Why you cannot just invert the light palette

A brand color tuned for white backgrounds is usually too dark and too saturated for dark ones. Take the blue #2f6bdf from a light theme. On white it passes as link text with 4.90:1. On #121212 it drops to 3.83:1 and Lc -27.7, which fails for text of any size.

The fix is to move up the brand scale: higher lightness, lower chroma. The table compares four steps of the same blue scale on both backgrounds.

StepColorOKLCHOn #ffffffOn #121212
600#2f6bdfoklch(55.5% 0.188 261.8)4.90:1, Lc 73.43.83:1, Lc -27.7
500#4a83f0oklch(62.7% 0.175 261.9)3.62:1, Lc 63.55.17:1, Lc -37.6
400#6f9ef4oklch(70.3% 0.137 261.7)2.67:1, Lc 51.77.02:1, Lc -49.6
300#a3c0f5oklch(80.5% 0.082 262.3)1.84:1, Lc 34.510.18:1, Lc -67.5

Step 300 is the dark-mode equivalent of step 600 for links and colored text: lightness goes from 0.555 to 0.805 and chroma drops from 0.188 to 0.082, less than half. The lower chroma matters too, because saturated colors on a dark background look like they glow and vibrate at the edges.

Filled buttons are different, because the button brings its own background. White text on #2f6bdf keeps 4.90:1 in both themes, so the primary button can stay the same. Only colored text, icons and outlines need the lighter step.

Elevation: lighter surfaces instead of shadows

Shadows barely show on a dark background, so dark themes signal height with lightness: the closer a surface is to the viewer, the lighter it gets. Material Design’s original dark theme did this with a white overlay on #121212, from 5% at 1 dp elevation, used for cards, to 16% at 24 dp, used for dialogs.

LevelWhite overlayResultOKLCH LAgainst base#e5e5e5 text
0 dp, background0%#1212120.1821:114.87:1
1 dp, card5%#1e1e1e0.2351.12:113.23:1
3 dp8%#2525250.2641.22:112.17:1
6 dp11%#2c2c2c0.2931.34:111.09:1
12 dp14%#3333330.3211.48:110.03:1
24 dp, dialog16%#3838380.3411.60:19.31:1

Each level adds roughly 0.02 to 0.05 of OKLCH lightness. Even the top level differs from the background by only 1.60:1, so add a 1 px border to surfaces that must stand out. In OKLCH you can write the same system without overlays: keep chroma and hue and raise L by about 0.03 per level.

prefers-color-scheme and the color-scheme property

The prefers-color-scheme media query tells you whether the operating system is set to light or dark. The color-scheme property tells the browser which schemes your page supports, so it can render scrollbars, form controls and the default canvas in matching colors.

Set color-scheme on the root, then override your color variables inside the media query.

CSS

:root {
    color-scheme: light dark;
    --bg: #ffffff;
    --surface: #f4f5f6;
    --text: #15171b;
    --text-muted: #6e7279;
    --link: #2f6bdf;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #121212;
        --surface: #1e1e1e;
        --text: #e5e5e5;
        --text-muted: #b2b2b2;
        --link: #a3c0f5;
    }
}

body {
    background: var(--bg);
    color: var(--text);
}

Without color-scheme, a dark page still shows a light scrollbar and white inputs in most browsers. It is one line and it is easy to forget.

The light-dark() function in CSS

light-dark() takes two colors and returns the first in a light scheme and the second in a dark one. It has worked in all major browsers since 2024: Chrome and Edge 123, Firefox 120 and Safari 17.5. It reads the color-scheme property: without color-scheme: light dark it always returns the light value.

Each color is declared once, next to its dark pair, and a theme switch only has to change color-scheme.

CSS

:root {
    color-scheme: light dark;
    --bg: light-dark(#ffffff, #121212);
    --surface: light-dark(#f4f5f6, #1e1e1e);
    --text: light-dark(#15171b, #e5e5e5);
    --text-muted: light-dark(#6e7279, #b2b2b2);
    --link: light-dark(#2f6bdf, #a3c0f5);
    --primary: #2f6bdf;
    --on-primary: #ffffff;
}

:root[data-theme='light'] {
    color-scheme: light;
}

:root[data-theme='dark'] {
    color-scheme: dark;
}

On the page background, every text color in this block passes AA in both themes. In the light theme: text 17.94:1, muted text 4.83:1, links 4.90:1. In the dark theme: text 14.87:1, muted text 8.84:1 with Lc -60.1, links 10.18:1. You can check any pair of your own in the PickShade contrast checker, which shows WCAG 2 and APCA side by side.

Questions and answers

What is the best background color for dark mode?

A very dark gray works better than pure black. #121212, recommended by Material Design, and #18181b are common choices. Off-white text such as #e5e5e5 on #121212 gives 14.87:1 in WCAG 2 and Lc -90.4 in APCA, readable without the glare of white on black.

Should I use pure black #000000 for dark mode?

Usually not for text-heavy screens. White text on pure black has the maximum contrast of 21:1, which causes halation: bright letters seem to bleed into the black and look blurry, especially for people with astigmatism. A dark gray background with off-white text reads more comfortably.

What text color should I use in dark mode?

Use an off-white such as #e5e5e5 for body text and a light gray around #b2b2b2 for secondary text. On #121212 they give 14.87:1 and 8.84:1. Avoid mid grays that only just pass 4.5:1, because on dark backgrounds they are much harder to read than the ratio suggests.

How do I adapt brand colors for dark mode?

Use a lighter, less saturated step of the same scale. A blue like #2f6bdf passes on white with 4.90:1 but gets only 3.83:1 on #121212. Its lighter step #a3c0f5 gets 10.18:1 on #121212, with OKLCH lightness raised from 0.555 to 0.805 and chroma lowered from 0.188 to 0.082.

Is the WCAG contrast ratio accurate for dark mode?

WCAG 2 overrates contrast between dark colors. A gray that just passes 4.5:1 on #121212 reaches only Lc -32.8 in APCA, while a gray that passes 4.5:1 on white reaches Lc 71.6. In dark mode aim for about 9:1 or more for body text, or check APCA as well.

What does light-dark() do in CSS?

light-dark() returns its first color when the element uses a light color scheme and its second when it uses a dark one. It needs the color-scheme property set, for example color-scheme: light dark on :root, and it works in all major browsers since 2024.

Try it in PickShade

Related guides