Dark Mode Chart Palettes

Permalink to "Dark Mode Chart Palettes"

Charts designed on white often fail on dark backgrounds: deep blues vanish, bright yellows glare, gridlines disappear or dominate and sequential scales read backwards. Users who choose dark mode often do so because of light sensitivity or low vision, so a careless dark theme hurts exactly the people it should help. Dark mode charts need their own palette, checked for contrast against the dark surface and tuned to avoid glare.

This guide belongs to chart colour and contrast.

Spec reference

Permalink to "Spec reference"
  • SC 1.4.11 Non-text Contrast (AA) — series and important graphical objects meet 3:1 against the background.
  • SC 1.4.3 Contrast (Minimum) (AA) — axis labels and data labels meet 4.5:1.
  • SC 1.4.1 Use of Color (A) — patterns and labels still needed in dark mode.
  • prefers-color-scheme — the media query that signals a dark preference.
Inverted light palette versus designed dark palette Comparison: inverting or reusing light palettes produces invisible dark series and glaring bright ones; a designed dark palette balances contrast and glare. Inverted light palette versus designed dark palette✗ Same palette on darkNavy series invisibleSaturated yellow glaresGridlines vanish or shoutSequential scale reversed✓ Separate dark paletteLightened blues at 3:1+Softer, less saturated tonesSubtle but visible gridlinesLightness direction re-checked

When to use — and when not to

Permalink to "When to use — and when not to"

Provide a dark palette whenever your app supports a dark theme or follows the system preference. Switch palettes through CSS variables so charts update with the theme.

Do not use filter: invert() on charts; it changes hues unpredictably and turns meaningful colours (red for loss) into their opposites. The misapplication: keeping the light palette on a dark background and calling it done.

Contrast of a blue series against dark background Bar chart of contrast ratios against a near-black background: navy light-mode blue 1.6 to 1; mid blue 3.4 to 1; lightened dark-mode blue 6.2 to 1. Contrast of a blue series against dark backgroundNavy (light palette)ratioMid blueratioLight blue (dark palette)ratio
Lighten series for dark surfaces.

Annotated code example

Permalink to "Annotated code example"
:root {
  --series-1: #155c38; --series-2: #8a6000; --series-3: #0d9488;
  --grid: #bfcfc4; --axis-text: #4a5c50; --chart-bg: #ffffff;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --series-1: #a8f0c8; --series-2: #ffe480; --series-3: #ccfbf1;   /* lighter, less saturated */
    --grid: #33413a; --axis-text: #d6f9e8; --chart-bg: #111714;
  }
}
:root[data-theme="dark"] { /* same values as above */ }
.chart .s1 { stroke: var(--series-1); } .chart .grid line { stroke: var(--grid); }
// Canvas charts read the variables at draw time and redraw on theme change
const mq = matchMedia('(prefers-color-scheme: dark)');
mq.addEventListener('change', () => chart.redraw(readPalette()));
function readPalette() { const s = getComputedStyle(document.documentElement); return [1, 2, 3].map(i => s.getPropertyValue(`--series-${i}`).trim()); }

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Concern Dark mode requirement
Focus ring Two-tone ring still visible on dark marks
Tooltips Background and text meet 4.5:1
Legend swatches Same colours as series in dark palette
Forced colours System colours override both palettes
Screen reader Unaffected; text carries meaning
Chart elements in light and dark themes Matrix of chart elements — series lines, gridlines, axis labels, tooltips, sequential scales — against light theme requirement, dark theme adjustment and check. Chart elements in light and dark themesLight themeDark adjustmentCheckSeries linesDark tonesLighter tones3:1 vs backgroundGridlinesLight greyDark greyVisible, not dominantAxis labelsDark textLight text4.5:1TooltipsLight surfaceDark surface4.5:1Sequential scalesLight → darkRe-check directionOrder clear
Every element needs a dark-mode decision.

Step-by-step implementation

Permalink to "Step-by-step implementation"
  1. Start from tokens. Define chart colours as CSS custom properties with light and dark values.
  2. Lighten and desaturate series. Raise lightness until each series meets 3:1 against the dark background, and reduce saturation to limit glare.
  3. Check series against each other. Adjacent series must remain distinguishable; patterns and direct labels still help.
  4. Tune supporting elements. Gridlines should be visible but quiet; axis labels need 4.5:1.
  5. Decide sequential direction. On dark backgrounds, many teams make high values lighter; whichever you choose, label the legend clearly.

Integration context

Permalink to "Integration context"

Light theme series contrast is covered in non-text contrast for chart series, scales in accessible sequential and diverging palettes, and forced colours in forced colors mode for SVG charts. Theme testing is in visual regression and theme testing, and tokens in design system tokens and component contracts.

Gotchas

Permalink to "Gotchas"

Hard-coded colours in libraries. Chart libraries often take colours as JavaScript options; read CSS variables at render time and redraw on theme change.

Exported images. Exports in dark mode produce dark images that look wrong in light documents; export with the light palette or let users choose.

Shadows and glows. Effects designed for light backgrounds can look muddy on dark; remove them.

Edge cases

Permalink to "Edge cases"
  • User toggles theme mid-session. Redraw canvas charts; SVG using CSS variables updates automatically.
  • Embedded charts. Charts in iframes need to know the parent theme; pass it explicitly.
  • High-contrast dark preferences. Some users combine dark mode with prefers-contrast: more; offer higher-contrast tokens.

Worked example

Permalink to "Worked example"

An analytics product added dark mode by swapping the page background but kept chart palettes. Navy and dark green series nearly disappeared, and a bright yellow series caused discomfort for users with light sensitivity. The team defined a dark palette with lightened, desaturated colours tested at 3:1 or more, adjusted gridlines and labels, and redrew canvas charts on theme change. Support tickets about unreadable dark charts stopped.

Common mistakes

Permalink to "Common mistakes"
  • Reusing the light palette.
  • Inverting charts with CSS filters.
  • Saturated colours that glare.
  • Forgetting tooltips and legends.

Testing checklist

Permalink to "Testing checklist"

Testing it with a screen reader

Permalink to "Testing it with a screen reader"

Screen readers are unaffected by palettes, so screen reader testing in dark mode checks that nothing else changed: names, descriptions and tables should be identical across themes. The more important checks are visual: toggle the system theme while the chart is open, confirm it redraws, and run a contrast checker on sampled series colours. Include low-vision users who use dark mode in usability testing.

Glare and halation

Permalink to "Glare and halation"

Some people, including many with astigmatism, see bright text and lines on dark backgrounds blur or glow, an effect called halation. Very high contrast — pure white on pure black — makes it worse. Use an off-black background and slightly off-white text, keep series lines at moderate brightness and avoid thin bright lines on large dark areas. Offer a light theme toggle that overrides the system preference for users who find dark mode harder to read.

Key points

Permalink to "Key points"
  • Design a dark palette; never invert or reuse the light one.
  • Lighten and soften series colours until each reaches 3:1 against the dark surface.
  • Re-check gridlines, axis labels, tooltips and legends, not just the series.
  • Redraw canvas charts when the theme changes, and cover both themes in visual regression tests.
  • Keep patterns and direct labels; colour vision deficiency does not change with the theme.

Checking palettes before release

Permalink to "Checking palettes before release"

Before shipping a dark palette, render every chart type in the product with real data in both themes and review them side by side. Check that series order and emphasis are the same in both: the series that stands out in light mode should still stand out in dark mode. Run contrast checks on every series and label colour, view the dark charts through colour vision deficiency simulators, and ask at least one user who prefers dark mode for comfort reasons to review them.

Design system notes

Permalink to "Design system notes"

Ship chart palettes as theme tokens with documented contrast ratios for both themes, and a helper that gives chart libraries the current palette.

FAQ

Permalink to "FAQ"
Can I invert the light palette?

No. Design a dark palette explicitly.

What contrast do series need?

At least 3:1 against the background.

Should high values be dark or light in dark mode?

Either, if the legend makes it clear; many choose lighter for higher.

How do I update canvas charts?

Listen for theme changes and redraw with new colours.

Do patterns still matter?

Yes; colour vision deficiency does not change with theme.

How do I test?

Visual regression in both themes plus contrast checks.

Permalink to "Related"

← Back to Chart Colour & Contrast