LUCKY.GRAPHICS
guides

Dark Mode Design Guide: Palettes, Contrast, and Tokens That Work

How to design dark mode properly: surface levels without shadows, desaturated brand colors, WCAG contrast targets, a worked palette with measured ratios, token mapping, and a QA checklist.

Lucky Graphics EditorialOctober 17, 202615 min

Dark Mode Design Guide: Palettes, Contrast, and Tokens That Work

Dark mode is not a color swap. Invert a light interface and you get gray text sinking into cards, a brand blue that vibrates against near-black, shadows that vanish, photos that glare, and chart lines that disappear. A dark palette is a set of relationships, measured pair by pair.

This guide builds one palette from scratch, with every ratio computed from the WCAG relative luminance formula, then maps it to tokens and covers surfaces, type, images, icons, charts, and QA.

The method in brief:

  1. Pick a base surface that is dark gray, not pure black.
  2. Build three lighter surface levels for cards, menus, dialogs.
  3. Set three text levels, tested against the highest surface each touches.
  4. Lighten and desaturate the brand color until it passes; pick dark text to sit on it.
  5. Rework success, warning, error, and info colors the same way.
  6. Give focus indicators and input borders their own tested colors.
  7. Store every value as a semantic token shared by both themes.
  8. Test on real screens, in a dim room and in daylight.

Why Pure Black Is Usually the Wrong Background

Pure black, hex 000000, is the first value most people reach for and the first one to remove.

Contrast overshoot. White text on pure black measures 21:1, the maximum the WCAG formula can produce. Maximum is not comfortable. Bright text on a fully black field glows for many readers, especially readers with astigmatism, where letter edges bleed into the background. Off-white text on dark gray lands in the 12:1 to 17:1 range used below: far above the 4.5:1 AA floor, with less glare.

No room for depth. A card, a header, and a modal need to read as separate layers. On black there is no darker tone for recessed areas and no shadow can show, so layers collapse into one flat field. Dark gray leaves space on both sides.

OLED smearing. On OLED screens, pure black pixels switch off, and scrolling content can smear as they switch back on. Dark gray keeps pixels lit at a low level and avoids most of that artifact.

True black has narrow uses: video players, photo viewers, presentation modes, where the content carries all the light. Those are viewing surfaces, not reading surfaces. For apps, dashboards, stores, and articles, start with dark gray.

Platform guidance points the same way. Material Design 3 describes its dark theme as a low-light interface displaying mostly dark surfaces, and pairs each surface role with an on-surface color chosen to meet contrast requirements. Apple describes Dark Mode as a system-wide darker palette for windows, views, menus, and controls, with more vibrancy so foreground content stands out.

Contrast Targets, Stated Exactly

WCAG 2.2 Success Criterion 1.4.3, Contrast (Minimum), sets the Level AA numbers used here:

  • Normal text: at least 4.5:1 against its background.
  • Large text: at least 3:1. Large means 18 point or larger, or 14 point bold or larger, about 24 px and about 18.5 px in CSS terms.
  • Level AAA raises normal text to 7:1 and large text to 4.5:1.

Four details in the W3C guidance change how you apply those numbers.

Do not round up. Ratios are threshold values and computed values should not be rounded: 4.499:1 does not meet a 4.5:1 requirement. Treat anything from 4.50 to about 4.80 as fragile; one token change can push it under.

Brand rules are not an exemption. The criterion exempts logotypes and incidental or decorative text, not text that fails because a brand guideline demands pale gray. If the brand color cannot carry body text, it becomes an accent and a tested color carries the text.

Every state counts. The requirement covers placeholder text and text shown on hover and keyboard focus. Failures cluster there: placeholder gray copied from the light theme, a hover row that lightens the background without re-testing its text.

Non-text elements have their own rule. Success Criterion 1.4.11 asks for 3:1 for interface components and graphics a reader needs to understand the content: input borders, focus indicators, toggles, chart marks. A hairline divider is decoration. The border that shows a field is a field is not.

Run your pairs through the contrast checker. Wider method: accessible color systems. Full compliance picture: WCAG compliance and accessibility guide.

The Worked Example: One Palette, Built Step by Step

The example product is a small analytics dashboard. Its light theme uses a saturated blue, hex 2563EB, as the primary color. Here is its dark palette, in build order.

Step 1: Fix the base surface

Pick the page background first, because every other color is measured against a surface. The base is hex 121417, dark gray with a slight blue cast. Below it sits an inset tone, hex 0C0E11, for code blocks and recessed areas with no small text on them.

Step 2: Build the surface ladder

Raised areas get lighter as they come forward:

  • Surface 1, hex 1A1D23: cards, sidebar, hover rows.
  • Surface 2, hex 21252D: menus, dropdowns, tooltips.
  • Surface 3, hex 2A2F38: dialogs, highest layers.

Each step is a small, even jump. If two levels look identical, the ladder has too many steps. Material Design 3 made the same move: tone-based surface roles replaced surfaces defined at set heights.

Step 3: Set three text levels

  • Text primary, hex F2F4F8: headings, body copy.
  • Text secondary, hex B9C2D0: captions, headers, labels.
  • Text tertiary, hex 939EAF: timestamps, hints.

Tertiary is where dark palettes fail. A muted gray that passes on the page background gets reused on a card, then on a dialog, and fails at the top of the ladder. So tertiary was chosen against Surface 3, its lightest background, at 4.96:1. It passes AA on all four surfaces.

Step 4: Desaturate and lighten the brand color

The light primary, hex 2563EB, measures 3.57:1 on hex 121417 and looks harsh: saturated dark blues shimmer against near-black. Raise lightness until it clears the targets, and lower saturation until it stops vibrating. The dark primary is hex 8AB4FF, a softer blue that keeps the brand hue, at 8.83:1 on the base. It carries links, labels, icons, and chart lines.

Filled buttons flip the pairing: pale blue fill, dark blue-gray text, hex 0B1020, at 9.06:1. White text on that pale fill would fail badly; a light fill with white text from the light theme is a common production defect.

Step 5: Rework the semantic colors

Success, warning, error, and info colors get lighter, less saturated versions that pass on the base and stay distinct: success hex 5FD68A at 10.08:1, warning hex F2C14E at 10.99:1, error hex FF8A80 at 8.08:1, info hex 6ED3E7 at 10.66:1.

Each status also carries an icon and a label: color is never the only signal, which matters most for error red and success green, the pair most often confused by readers with color vision deficiency. See our color theory guide.

Step 6: Give borders and focus their own colors

Two border colors, two jobs. A decorative divider, hex 4A5468, separates sections where the surface change already shows the boundary; at 2.42:1 it is a visual aid, not information. A control border, hex 6B778D, marks fields and toggles, so it meets the non-text rule: 4.08:1 on the base, 3.74:1 on Surface 1. The focus indicator is a separate amber, hex FFD166, chosen so it never blends into a primary button: 12.80:1 on the base, 10.65:1 on Surface 2.

The measured palette table

Ratios below were computed with the WCAG formula: linearize each sRGB channel, weight by 0.2126, 0.7152, and 0.0722 for relative luminance, then divide lighter plus 0.05 by darker plus 0.05.

ForegroundHexBackgroundHexRatioUse, AA result
Text primaryF2F4F8Surface 012141716.76:1Body, passes AAA
Text primaryF2F4F8Surface 11A1D2315.33:1Cards, passes AAA
Text primaryF2F4F8Surface 221252D13.95:1Menus, passes AAA
Text primaryF2F4F8Surface 32A2F3812.21:1Dialogs, passes AAA
Text secondaryB9C2D0Surface 012141710.27:1Support text, passes AAA
Text secondaryB9C2D0Surface 221252D8.55:1Panel labels, passes AAA
Text tertiary939EAFSurface 01214176.81:1Small text, passes AA
Text tertiary939EAFSurface 32A2F384.96:1Dialog small text, passes AA
Primary8AB4FFSurface 01214178.83:1Links, passes AAA
Primary8AB4FFSurface 11A1D238.08:1Card links, passes AAA
On-primary0B1020Primary fill8AB4FF9.06:1Button text, passes AAA
Success5FD68ASurface 012141710.08:1Status, passes
WarningF2C14ESurface 012141710.99:1Status, passes
ErrorFF8A80Surface 01214178.08:1Status, passes
Info6ED3E7Surface 012141710.66:1Status, passes
Focus indicatorFFD166Surface 012141712.80:1Focus ring, passes 3:1
Focus indicatorFFD166Surface 221252D10.65:1Focus on panels, passes 3:1
Control border6B778DSurface 01214174.08:1Inputs, passes 3:1
Control border6B778DSurface 11A1D233.74:1Card inputs, passes 3:1

Copy this format: one row per real pair. Colors listed without pairs prove nothing.

Surface Levels Without Shadows

Shadows depend on a light background. A dark shadow on a dark surface is invisible, and a shadow darker than the page makes a card look sunken. Dark mode builds depth from three tools.

Lighter surfaces for higher layers. The ladder from Step 2 does most of the work: page at the base, cards one step up, menus two steps, dialogs three. A reader learns it fast and can tell from tone which layer dismisses on Escape.

Borders as edges. A 1 px border in the decorative border color gives a card a crisp edge where tones are close, and survives screenshots and poor displays better than a tone difference.

Darker tones for recessed areas. Wells, code blocks, and toggle tracks sit one step below the surface around them: recession shown by going darker, the reverse of shading an inset with an inner shadow.

Reserve real shadows for dialogs or menus over busy content, where a soft, wide, low-opacity shadow separates the layer from what sits behind it.

Typography in Dark Mode

Light text on dark looks thinner than dark text on light. Thin strokes lose light to the surrounding darkness, an effect called halation, so small text at the light-theme weight reads slower.

  • Raise body text one weight step in dark mode, regular to medium at small sizes. Large headings usually keep their weight.
  • Add a little line height for long reading text, about 0.05 to 0.1, to reduce the dense, striped look of a dark page.
  • Do not set body text in pure white. Hex F2F4F8 cuts glare and still measures 16.76:1 on the base.
  • Avoid light weights for anything functional. A 300-weight caption that looks refined on white becomes faint thread on dark gray. The W3C notes on thin fonts and anti-aliasing agree: a pair can pass on paper and read weaker on screen.
  • Test table numbers at real size: figures and units are misread first when weight drops.

Images, Icons, and Data Visualization

Photographs and illustrations. A full-brightness photo on a dark page becomes the brightest object on screen. Many products reduce image brightness slightly in dark mode, around 85 to 90 percent, restoring full brightness in a viewer. Apply that to decorative imagery only, never to a product photo a buyer is judging or any image whose color is the information. Logos need a dark variant or a light holding shape: a dark wordmark on transparency disappears.

Icons. Outline icons drawn thin in mid-gray are a repeat failure. Give icons the text primary or secondary color for their surface, keep stroke weights at or above the light-theme weight, and use filled variants where the outline reads as a faint sketch. Meaning never rides on color alone: pair color with shape, position, or a label.

Charts. Gridlines copied from the light theme are either invisible or, lightened carelessly, brighter than the data. Set gridlines just above the surface tone, axis labels in text secondary, and series in the reworked palette colors, measured against the chart surface, not the page surface. One series at least should be identifiable by dash, marker, or label instead of hue alone. Full method: chart color accessibility checklist.

Video and embeds. Use the dark variant of any embed that offers one, with a border in the control border color. A bright player or light-styled map dominates a dark page.

From Palette to Tokens

A palette stored as raw hex values in component files drifts within one release cycle. Store roles instead: each component asks for a role, such as surface-raised or text-secondary, and each theme supplies the value. Material Design 3 is built this way.

TokenLightDarkNotes
color-surface-baseFFFFFF121417Page background
color-surface-raised-1F4F5F71A1D23Cards, sidebar
color-surface-raised-2ECEEF121252DMenus, dropdowns
color-surface-raised-3E2E5EA2A2F38Dialogs
color-surface-insetECEEF10C0E11Wells, code
color-text-primary16181DF2F4F8Headings, body
color-text-secondary4B5563B9C2D0Captions, labels
color-text-tertiary667085939EAFHints, timestamps
color-primary2563EB8AB4FFLinks, accents
color-on-primaryFFFFFF0B1020Text on primary fill
color-success0676475FD68AAdd icon and label
color-warningB54708F2C14EAdd icon and label
color-errorB42318FF8A80Add icon and label
color-info026AA26ED3E7Add icon and label
color-border-subtleD0D5DD4A5468Decorative dividers
color-border-control6670856B778DInputs, must pass 3:1
color-focus2563EBFFD166Separate from primary
shadow-cardSoft gray shadowNoneTone plus border instead

Three rules keep the token layer clean. No component names a hex value; a change that adds one is a defect. A new color enters as a token with a measured pair, not a one-off style. State colors are tokens too: hover, pressed, selected, and disabled each get a value per theme, because component-level states are where untested pairs start.

Implementation and Handoff Checklist

For the build:

  • Respect the system setting by default, using the prefers-color-scheme media query on the web or the platform appearance setting in native apps. Store a manual override per user; a resetting toggle teaches users the setting is decorative.
  • Ship both themes from one token file or theme object. Two hand-maintained stylesheets will diverge.
  • Set browser chrome and overscroll background per theme, so a dark page does not flash a white frame while loading.
  • Provide dark variants or holding shapes for logos, white-background illustrations, and favicons.
  • Check form controls, scrollbars, selection color, autofill styling, and forced-colors mode where offered.

For the handoff:

  • Hand over the palette as the pair table: foreground, background, ratio, job. A swatch sheet is not a spec.
  • Name tokens in the design file to match code token names exactly.
  • Include one reference screen per surface level, with text, a form, a chart, and a dialog, in both themes.
  • Flag pairs with the least headroom, anything under 5:1 for text, and state the image dimming rule in writing.

QA Checklist Before You Ship

  • Every text pair measured on the surface it renders on, including hover, focus, selected, and placeholder states.
  • Tertiary text tested on the highest surface it touches.
  • Primary buttons checked as a pair, fill and on-primary together, in both themes.
  • Focus indicator visible on every surface level, at keyboard speed.
  • Input borders at 3:1 or better against the surface outside the field.
  • Charts checked against the chart surface, plus one color vision deficiency simulator pass.
  • Images checked for dark logos, white-background illustrations, dominant photos.
  • One pass on a real phone in a dim room, and one in daylight.
  • Theme toggle tested for persistence; first paint checked for a wrong-theme flash.
  • One keyboard and screen reader pass in dark mode.

FAQ

Should my dark background be pure black?

For most interfaces, no. Dark gray such as hex 121417 keeps contrast high without glare, leaves room for recessed and raised areas, and avoids OLED scrolling smear. Save pure black for video and photo players.

What contrast ratio does dark mode body text need?

The same as light mode: at least 4.5:1 at WCAG Level AA, 7:1 at AAA. This palette sets body text at 16.76:1 on its base and tertiary text at 4.96:1 on its lightest surface. Measure each text color against each surface it sits on.

Can I keep my brand color unchanged in dark mode?

Usually not as-is. A saturated brand color built for white tends to sit near 3:1 to 4:1 on a dark surface and look harsh. Lighten it and reduce saturation until it passes with headroom, as in the move from hex 2563EB to hex 8AB4FF.

Do images need changes for dark mode?

Often, yes. Bright photos can dominate a dark page, dark logos can vanish, and white-background illustrations glare. Dimming decorative images, dark logo variants or holding shapes, and dark embeds cover most cases. Never dim an image whose color is the information.

Is a manual theme toggle required if the site follows the system setting?

A system-based default is the right start, and a manual override is strongly advised. People set their system to dark for one app or one room and still need your reading pages in light, or the reverse. Store the choice.

Sources Read for This Guide

All ratios in the palette table were computed for this guide from the hex values printed there.

What to Read Next

Maintained by the Lucky Graphics editorial team. Measure your own product pairs before shipping.

Lucky Graphics Editorial

This guide is maintained by the site editorial team. Sources, limitations, and practical checks are included in the article so readers can verify important details.

Tags
#dark mode#color palette#contrast#design tokens#accessibility#WCAG

Found this helpful?

Share this guide with your network

Continue Reading

Ready to Put This Into Practice?

Browse our curated collection of design assets to find the perfect resources for your next project.

Explore Assets