Accessible Sequential and Diverging Palettes
Permalink to "Accessible Sequential and Diverging Palettes"Heatmaps, choropleth maps and density plots encode values with colour scales. Two kinds matter most: sequential scales (low to high) and diverging scales (below to above a midpoint). Rainbow scales and red–green diverging scales are still common and fail many users: they do not change lightness evenly, so order is lost in greyscale and for people with colour vision deficiency. Accessible palettes vary mainly in lightness, use colour-blind-safe hue pairs and are backed by labels and legends with values.
This guide belongs to chart colour and contrast.
Spec reference
Permalink to "Spec reference"- SC 1.4.1 Use of Color (A) — values readable without relying on hue (labels, tooltips, tables).
- SC 1.4.11 Non-text Contrast (AA) — graphical objects needed to understand content meet 3:1 against adjacent colours; for scales, the lightest step against the background matters most.
- SC 1.4.3 Contrast (Minimum) (AA) — text labels on coloured cells need 4.5:1.
- Perceptual uniformity — equal data steps should look like equal colour steps.
When to use — and when not to
Permalink to "When to use — and when not to"Use sequential scales for data with a natural order from low to high (counts, rates). Use diverging scales when there is a meaningful midpoint (zero change, the national average).
Do not use a diverging scale for purely sequential data; it implies a midpoint that does not exist. The misapplication: a rainbow scale where yellow (bright) sits in the middle, so mid values look most important and order is lost in greyscale.
Annotated code example
Permalink to "Annotated code example"// Sequential: one hue, lightness steps (values from a perceptual scale)
const sequential = ['#edfdf4', '#a8f0c8', '#4fc98a', '#219150', '#155c38']; // light → dark
// Diverging: two hues with a light neutral middle (blue–orange is colour-blind safe)
const diverging = ['#1d4e89', '#7aa6d6', '#f2f2f2', '#f0a35e', '#a3480f'];
function colourFor(v, scale, domain) {
const i = Math.min(scale.length - 1, Math.floor((v - domain[0]) / (domain[1] - domain[0]) * scale.length));
return scale[i];
}
function labelColour(bg) { return contrast(bg, '#111714') >= 4.5 ? '#111714' : '#ffffff'; } // text on cells
<ul class="scale-legend" aria-label="Legend: change in sales">
<li><span class="sw" style="background:#1d4e89" aria-hidden="true"></span> −20% or more</li>
<li><span class="sw" style="background:#f2f2f2" aria-hidden="true"></span> −5% to +5% (no change)</li>
<li><span class="sw" style="background:#a3480f" aria-hidden="true"></span> +20% or more</li>
</ul>
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Element | Screen reader experience |
|---|---|
| Legend list | Each range read as text |
| Heatmap cell focus | “North, March, 1,240 (high)” — value plus band name |
| Choropleth region | “Oslo: 18% above average” |
| Data table alternative | Exact values |
| Colour itself | Not announced; text carries meaning |
Step-by-step implementation
Permalink to "Step-by-step implementation"- Classify the data. Ordered (sequential) or centred on a meaningful value (diverging)?
- Pick a tested palette. Use established perceptual palettes (single-hue ramps, viridis-like, ColorBrewer colour-blind-safe sets) rather than hand-picking.
- Choose the number of steps. Five to seven classes are distinguishable for most users; continuous scales need a labelled legend with ticks.
- Check contrast. The lightest step against the page background should meet 3:1 if cell boundaries matter; add borders otherwise. Text on cells must meet 4.5:1, switching between dark and light labels.
- Simulate. View in protanopia, deuteranopia and tritanopia simulators and in greyscale; order must remain clear.
Integration context
Permalink to "Integration context"Contrast between series is covered in non-text contrast for chart series, and patterns in patterns and shapes for colour-independent charts. Heatmap structure is in accessible heatmaps, choropleths in table alternatives for choropleth maps, and dark themes in dark mode chart palettes.
Gotchas
Permalink to "Gotchas"Lightest step invisible. A near-white first step disappears on white backgrounds; add a cell border or start the scale slightly darker.
Dark mode inversion. In dark themes, reverse the lightness direction or “high” looks low; test both.
Legend precision. A gradient bar without labelled ticks forces users to judge colour; always label ranges.
Edge cases
Permalink to "Edge cases"- Missing data. Use a distinct pattern (hatching) for no data, not a colour from the scale.
- Outliers. A single extreme value can compress the scale; use quantiles or clamp and say so in the legend.
- Printing. Sequential lightness scales print well in greyscale; diverging scales need distinct ends.
Worked example
Permalink to "Worked example"A public health dashboard used a rainbow scale for case rates by district. Colour-blind readers could not order districts, and the bright yellow middle drew attention to average areas. The team switched to a five-step single-hue sequential scale with a labelled legend, added dark or light value labels on each district and a hatched pattern for missing data. In usability tests, participants with colour vision deficiency ranked districts correctly.
Common mistakes
Permalink to "Common mistakes"- Rainbow scales.
- Red–green diverging scales.
- Too many steps.
- Unlabelled gradient legends.
Testing checklist
Permalink to "Testing checklist"Testing it with a screen reader
Permalink to "Testing it with a screen reader"Colour is invisible to screen readers, so test that meaning does not depend on it. With NVDA, navigate the heatmap or map regions and confirm each announces a value and, where useful, a band name (“high”). Read the legend as text and check every range is described. Then confirm a data table is available with exact values. If any information is only available through colour, add text.
Naming bands
Permalink to "Naming bands"Band names in announcements and tooltips help users who cannot compare colours. Use plain words tied to the legend ranges: “very low”, “low”, “average”, “high”, “very high”, or domain terms such as “below target”. Keep the number of names equal to the number of steps, and show the same names in the legend. For diverging scales, include the direction: “well below average”, “above average”. Avoid names that imply judgement unless the data has one.
Choosing class breaks
Permalink to "Choosing class breaks"How values are split into classes changes what users see as much as the colours do. Equal intervals are easy to explain but bunch skewed data into one or two classes. Quantiles put the same number of items in each class, which spreads colours but can make tiny differences look large. Natural breaks follow clusters in the data. Whichever you choose, state it in the legend (“Classes: quintiles”) and show the numeric range for each class, so users can interpret colours correctly without guessing the method.
Design system notes
Permalink to "Design system notes"Publish approved sequential and diverging palettes as tokens with light and dark variants, label colour rules and missing-data patterns.
FAQ
Permalink to "FAQ"Is viridis accessible?
It is perceptually uniform and colour-blind friendly; check the lightest step against your background.
How many classes should I use?
Five to seven for most purposes.
Is red–green ever acceptable?
Avoid it; blue–orange or purple–green work better.
Do I need labels on every cell?
Not always, but values must be available via tooltip, focus or table.
What about continuous scales?
Use a labelled gradient legend with tick values.
Should the midpoint be white?
A light neutral works; ensure it differs from the background or add borders.
Related
Permalink to "Related"- Dark mode chart palettes — dark themes
- Accessible heatmaps — applying scales
- Non-text contrast for chart series — categorical colours