Non-Text Contrast for Chart Series

Permalink to "Non-Text Contrast for Chart Series"

SC 1.4.11 Non-text Contrast requires that graphical objects needed to understand content have a contrast ratio of at least 3:1 against adjacent colours. In a chart, that means the line, the bars, the points and the focus indicator — the marks that carry data — must stand out from the background they sit on and from each other where they touch. Pastel palettes on white backgrounds, and saturated palettes on dark backgrounds that were never checked, are the common failures: a light yellow line at 1.4:1 against white is, for many low-vision users, not there.

This page defines what the criterion covers in a chart, how to measure it in both light and dark themes, and how to handle adjacent segments in stacked bars and pies. It belongs to chart colour & contrast.

Spec reference

Permalink to "Spec reference"

SC 1.4.11 Non-text Contrast (Level AA): visual information required to identify user interface components and states, and parts of graphics required to understand the content, have a contrast ratio of at least 3:1 against adjacent colour(s).

For charts, the understanding document is explicit that data lines, bars and points are graphics required to understand the content. It also notes an important nuance: if the information is available in another form — data labels on every bar, an adjacent table — the graphic’s contrast may matter less for comprehension, but the chart is still expected to be perceivable on its own for users who look at it. In practice, aim for 3:1 on every data mark.

Contrast is computed with the same relative luminance formula as text contrast. Text in charts — axis labels, data labels, legends — falls under SC 1.4.3 (4.5:1, or 3:1 for large text), not 1.4.11.

Which chart parts need 3:1 Matrix of chart parts, whether each falls under SC 1.4.11 non-text contrast, SC 1.4.3 text contrast or neither, and the adjacent colour to measure against. Which chart parts need 3:1Chart partRequirementMeasure againstLine, bar, point, slice1.4.11 — 3:1Plot backgroundAdjacent stacked segments1.4.11 — 3:1 or a gapEach otherFocus ring on a point1.4.11 — 3:1Background and the pointAxis labels, legends1.4.3 — 4.5:1BackgroundDecorative gridlinesNone—
Data marks and focus rings need 3:1; text needs 4.5:1; decorative gridlines need neither.

When 3:1 is enough — and when to do more

Permalink to "When 3:1 is enough — and when to do more"

3:1 against the background is the floor for every data mark. It makes a single series visible.

It does not make several series distinguishable. Two lines can each have 3:1 against white and still look nearly identical to each other, especially for colour-blind users. Distinguishing series is a separate problem — solved with shapes, dashes, patterns and direct labels, not with more contrast — see patterns and shapes for colour-independent charts.

Adjacent areas — stacked bar segments, pie slices, treemap tiles — need 3:1 against each other where they touch, or a separator. A thin background-coloured gap between segments is usually easier than finding a palette where every neighbouring pair reaches 3:1.

The misapplication to name is checking the palette only in the light theme. A dark theme that reuses the same mid-tone palette on a near-black background can fail for the darker series and pass for the lighter ones — the opposite of the light theme.

Annotated code example

Permalink to "Annotated code example"
/* Series tokens per theme — each checked at 3:1 against its plot background */
:root {
  --plot-bg: #ffffff;
  --series-1: #155c38;   /* 8.0:1 on white */
  --series-2: #8a6000;   /* 5.6:1 on white */
  --series-3: #0d9488;   /* 3.7:1 on white */
  --focus-ring: #111714;
}
[data-theme="dark"] {
  --plot-bg: #141d18;
  --series-1: #6ddfa0;   /* 10.4:1 on dark */
  --series-2: #ffd033;   /* 11.8:1 on dark */
  --series-3: #2dd4bf;   /* 9.3:1 on dark */
  --focus-ring: #e7f0ea;
}

/* SC 1.4.11: adjacent stacked segments separated by a background-coloured gap */
.stack-segment { stroke: var(--plot-bg); stroke-width: 2px; }
// Contrast check for series tokens in CI (both themes)
function luminance(hex) {
  const [r, g, b] = hex.match(/\w\w/g).map((h) => {
    const c = parseInt(h, 16) / 255;
    return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
  });
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
const ratio = (a, b) => {
  const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
  return (hi + 0.05) / (lo + 0.05);
};
for (const theme of themes) {
  for (const s of theme.series) {
    if (ratio(s, theme.plotBg) < 3) throw new Error(`${theme.name}: ${s} is ${ratio(s, theme.plotBg).toFixed(2)}:1`);
  }
}

The ratios in the comments are computed with exactly this formula. Checking tokens in CI catches palette changes before they reach a chart; checking rendered charts (with a screenshot and colour sampling) catches cases where a library ignored the tokens.

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"

Contrast is visual, but it interacts with keyboard use at one point: the focus indicator on a chart’s current data point.

Situation Requirement Failure indicator
Focused point on a line Ring 3:1 against background and point Ring same colour as the series
Focused bar Outline 3:1 against bar and background Lighter tint of the bar colour
Dark theme focus Ring colour switched per theme Dark ring on dark background
High zoom Ring scales with the chart 1px ring vanishes at 400%
Contrast of three series colours against white Bar chart of the computed contrast ratio of three series colours against a white plot background, compared with the 3:1 threshold, including one failing pastel. Contrast of three series colours against white#155c38 green8.0:1#8a6000 amber5.6:1#0d9488 teal3.7:1#f5e27a pastel yellow1.3:1 — fails 3:1
Computed with the relative luminance formula above; the pastel yellow is what many default palettes ship.

Integration context

Permalink to "Integration context"

Colour is only one encoding; pairing it with shape, dash and direct labels is what makes series distinguishable for everyone, covered in patterns and shapes for colour-independent charts and direct labelling instead of legends. In Windows High Contrast and other forced-colours modes, your palette is replaced entirely; see forced colours mode for SVG charts.

The measurement technique for focus rings on data points is the same as for any control; auditing focus indicator contrast for WCAG 2.4.11 covers it.

Separating adjacent segments Comparison of trying to find a palette where every neighbouring stacked segment pair reaches 3:1 against separating segments with a background-coloured gap. Separating adjacent segments✗ Contrast between every pairPalettes of 5+ colours rarely manage itBreaks again in dark modeFragile when series order changes✓ Background-coloured gapWorks for any number of segmentsAdapts via the plot background tokenOrder-independent
A 2px gap turns an almost impossible palette constraint into a trivial one.

Gotchas

Permalink to "Gotchas"

Opacity. Semi-transparent area fills blend with the background and gridlines; compute contrast on the blended colour, not the token.

Thin lines. A 1px line at exactly 3:1 is technically compliant and hard to see. Use 2px or more for data lines.

Hover dimming. Libraries that fade non-hovered series to 20% opacity can drop them below 3:1 exactly when users are comparing. Dim less, or add an outline to the hovered series instead.

Design system notes

Permalink to "Design system notes"

Define chart series as design tokens per theme, generated and checked together: each token with its computed ratio against the plot background in both themes, recorded in the token documentation. Provide a separator token equal to the plot background for stacked charts. Chart components should read tokens rather than library defaults.

Testing checklist

Permalink to "Testing checklist"

FAQ

Permalink to "FAQ"
Does SC 1.4.11 apply to chart lines and bars?

Yes. Parts of graphics required to understand content, such as data lines, bars, points and slices, need at least 3:1 contrast against adjacent colours.

Do adjacent stacked bar segments need 3:1 against each other?

Where they touch, yes, or they need a separator. The simplest fix is a thin gap in the background colour between segments, so each segment only needs 3:1 against the background.

Is 3:1 contrast enough to tell series apart?

No. It makes each series visible, but series can each pass 3:1 and still look alike, especially to colour-blind users. Add shapes, dash patterns or direct labels.

Do chart colours need to be checked in dark mode separately?

Yes. A palette that passes on white can fail on a dark background, and vice versa. Define series colours per theme and check each against its own plot background.

Permalink to "Related"

← Back to Chart Colour & Contrast