Direct Labelling Instead of Legends
Permalink to "Direct Labelling Instead of Legends"A legend asks readers to look at a colour swatch, remember the colour, find the matching line, and repeat for every series. That is a memory task that colour-blind users often cannot complete at all, and that low-vision users at high zoom struggle with because the legend and the line are far apart on screen. Direct labelling removes the task: each series is named in text right next to its data — at the end of a line, above a bar group, inside a large segment.
This page covers placement rules, collision handling, contrast, and how direct labels relate to the chart’s text alternative. It belongs to chart colour & contrast.
Spec reference
Permalink to "Spec reference"Direct labels satisfy SC 1.4.1 Use of Color by carrying series identity in text rather than in colour. As text, they fall under SC 1.4.3 Contrast (Minimum) — 4.5:1 for normal text, 3:1 for large (18.66px bold or 24px regular) — and SC 1.4.4 Resize Text.
In an inline SVG, labels are <text> elements, which screen readers can read if the SVG is not treated as a single image. For a chart exposed as role="img" with a name and description, the labels are presentational to assistive technology, and the series names belong in the description or data table instead. For a chart exposed as a structured graphic (for example, a role="group" with focusable points), each point’s name should include its series.
Related: SC 1.3.3 Sensory Characteristics — instructions should not rely on colour or position alone (“the green line”, “the series on the right”).
When to label directly — and when a legend is still needed
Permalink to "When to label directly — and when a legend is still needed"Label directly whenever there is room: line charts with up to about six series, grouped bar charts, stacked bars with large segments, slope charts. It is almost always clearer.
Keep a legend (or add a text key) when labels would overlap unavoidably — ten tangled lines, small multiples sharing a key, very narrow charts — and pair it with shapes or dashes so the legend is not colour-only. A legend rendered as HTML text next to the chart is also better for screen readers than one drawn inside a canvas.
The misapplication to name is direct labels that are themselves colour-only — tiny coloured dots at line ends with the name in a tooltip. The text must be visible.
Annotated code example
Permalink to "Annotated code example"// D3-style direct labels at line ends, with simple collision avoidance
const LABEL_GAP = 16; // minimum vertical distance, px
function placeEndLabels(series, x, y, width) {
const labels = series.map((s) => {
const last = s.values.at(-1);
return { name: s.name, token: s.token, x: x(last.date) + 8, y: y(last.value) };
}).sort((a, b) => a.y - b.y);
// push labels apart top to bottom, then clamp to the plot
for (let i = 1; i < labels.length; i++) {
if (labels[i].y - labels[i - 1].y < LABEL_GAP) labels[i].y = labels[i - 1].y + LABEL_GAP;
}
return labels.map((l) => ({ ...l, y: Math.min(l.y, plotHeight - 4) }));
}
function renderLabels(g, labels) {
g.selectAll('text.series-label').data(labels).join('text')
.attr('class', 'series-label')
.attr('x', (d) => d.x).attr('y', (d) => d.y).attr('dy', '0.35em')
// SC 1.4.3: label text colour chosen for 4.5:1, echoing the series token
.attr('fill', (d) => `var(${d.token}-text)`)
.text((d) => d.name);
}
/* Label text tokens: darker (light theme) or lighter (dark theme) than the line colour */
:root { --series-1-text: #0d3320; --series-2-text: #6b4a00; }
[data-theme="dark"] { --series-1-text: #6ddfa0; --series-2-text: #ffd033; }
.series-label { font: 600 0.8125rem/1 Inter, system-ui, sans-serif; }
Using a separate -text token for label colour lets the line keep a mid-tone colour (fine at 3:1 as a graphic) while its label uses a darker or lighter shade that reaches 4.5:1 as text. The shared hue keeps the visual association.
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Chart exposure | What screen readers get | What to do |
|---|---|---|
role="img" with name and description |
Name and description only | Include series names and their trends in the description |
| Structured SVG with focusable points | Each point’s accessible name | Include the series: “Web, March, 1,240” |
| Canvas | Nothing from labels | Description + data table with series as columns |
| HTML legend beside the chart | Legend text, if kept | Mark it as a list with the same order as labels |
Integration context
Permalink to "Integration context"Direct labels are one of two colour-free encodings; the other, shapes and patterns, is in patterns and shapes for colour-independent charts. Many charts benefit from both: dashes distinguish the lines along their length, labels name them at the end.
The chart’s text alternative should mention each series by the same name as its label — generating accessible text alternatives for D3 charts covers generating that summary from the data.
Gotchas
Permalink to "Gotchas"Lines that end at different x. Series with different end dates need labels at their own last points, not aligned in a column. Check for collisions with the next series’ line, not only labels.
Long names. Reserve right-hand margin for labels; truncating series names defeats the purpose. Wrap onto two lines if needed.
Resize text at 200%. Labels in SVG scale with the chart, not with user text-size preferences. Size them in rem-derived units where the chart is HTML-overlay based, or give the chart enough size that labels remain readable at zoom.
Design system notes
Permalink to "Design system notes"Chart components should default to direct labels when the series count is small, falling back to an HTML legend with shape and dash swatches above a threshold. Provide -text variants of each series token that meet 4.5:1 in both themes, alongside the mark colour tokens.
Testing checklist
Permalink to "Testing checklist"FAQ
Permalink to "FAQ"Why use direct labels instead of a legend?
Direct labels put the series name next to its data, so readers do not have to match colours between a legend and the chart. That helps colour-blind users, low-vision users at high zoom, and anyone reading quickly.
Do direct labels need to meet text contrast?
Yes. They are text, so they need 4.5:1, or 3:1 if large. Use a darker or lighter shade of the series hue for the label than for the line if the line colour itself is too light.
What if labels overlap?
Sort labels by position and push overlapping ones apart, adding thin leader lines if a label moves far from its line. If that still fails, fall back to a legend that uses shapes or dashes as well as colour.
Do screen readers read direct labels?
Only if the chart is exposed as structured content. For charts exposed as a single image, put the series names in the description and the data table instead.
Related
Permalink to "Related"- Patterns and shapes — the other second channel
- Non-text contrast — contrast for marks and labels
- Text alternatives for D3 charts — labels in the text alternative