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.
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% |
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.
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.
Related
Permalink to "Related"- Patterns and shapes — the second encoding
- Forced colours mode — when the palette is replaced
- Auditing focus indicator contrast — the same measurement for focus rings