Accessible Stacked Bar Charts
Permalink to "Accessible Stacked Bar Charts"Stacked bar charts answer two questions at once: how large is each total, and what makes it up? Revenue by quarter split by product line; tickets by week split by priority. Sighted users read totals from bar heights and segment sizes from colours. That double encoding is exactly what is lost for screen reader users if segments are anonymous, and for colour-blind users if adjacent segments use similar hues. Two-dimensional keyboard navigation and a well-structured table bring both questions back.
This guide belongs to chart type-specific patterns.
Spec reference
Permalink to "Spec reference"- SC 1.1.1 Non-text Content (A) — summary plus table.
- SC 1.3.1 Info and Relationships (A) — each segment belongs to a category and a series; the table’s row and column headers express both.
- SC 1.4.1 Use of Color (A) and SC 1.4.11 Non-text Contrast (AA) — adjacent segments need a non-colour distinction (border, pattern, label) and sufficient contrast.
- SC 2.1.1 Keyboard (A) — segment details on hover must be available on focus.
When to use — and when not to
Permalink to "When to use — and when not to"Use stacked bars when both the total and its composition matter and there are few series (up to four or five). Use grouped bars when users need to compare one series across categories precisely; only the bottom segment of a stacked bar shares a baseline.
Do not stack many series with similar colours; the middle segments become unreadable for everyone. The misapplication: a 100%-stacked chart without totals anywhere — users lose the sense of scale entirely.
Annotated code example
Permalink to "Annotated code example"<figure aria-labelledby="sb-t" aria-describedby="sb-s">
<h3 id="sb-t">Revenue by quarter and product line</h3>
<p id="sb-s">Revenue grew from 4.1 M in Q1 to 5.4 M in Q3. Software is the largest line every quarter, 57% of Q2.</p>
<svg viewBox="0 0 20 20" role="application" aria-label="Revenue by quarter, stacked bars. Left and Right move between quarters, Up and Down between product lines." tabindex="0">…</svg>
<p id="sb-status" role="status" class="visually-hidden"></p>
</figure>
// 2D navigation: bars (categories) × segments (series)
let b = 0, s = 0;
svg.addEventListener('keydown', (e) => {
const moves = { ArrowRight: [1, 0], ArrowLeft: [-1, 0], ArrowUp: [0, 1], ArrowDown: [0, -1] };
const m = moves[e.key]; if (!m) return;
e.preventDefault();
b = clamp(b + m[0], 0, data.length - 1); s = clamp(s + m[1], 0, series.length - 1);
const v = data[b][series[s]]; const total = sum(data[b]);
highlight(b, s); // visible focus (SC 2.4.7)
status.textContent = `${data[b].label}, ${series[s]}: ${fmt(v)}, ${pct(v / total)} of ${fmt(total)}`;
});
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Key | Action | Expected announcement | AT deviations |
|---|---|---|---|
| Tab | Focus chart | Name and key instructions | Application mode in NVDA/JAWS |
| Right / Left | Next / previous quarter, same series | “Q3, Software: 2.9 M, 54% of 5.4 M” | — |
| Up / Down | Next / previous series, same quarter | “Q3, Services: 1.1 M, 20% of 5.4 M” | — |
| Home / End | First / last quarter | As above | — |
Integration context
Permalink to "Integration context"Two-dimensional keyboard navigation extends adding keyboard navigation to canvas bar charts. Segment separation and patterns follow patterns and shapes for colour-independent charts and non-text contrast for chart series. Totals in tables use the conventions of announcing totals and summary rows.
Gotchas
Permalink to "Gotchas"Negative values. Stacking positive and negative values above and below the axis confuses everyone. Use grouped bars or separate charts.
100% stacked charts. If you normalise to 100%, still give absolute totals in the table and in announcements.
Series order. Keep series in the same order in the stack, the legend and the table, so Up and Down match what users see.
Common mistakes
Permalink to "Common mistakes"- Segments with only a value, no series or share.
- Similar hues for adjacent segments, no separators.
- No total column in the table.
- Up and Down moving in the opposite visual direction to the stack.
Framework notes
Permalink to "Framework notes"Highcharts stacked columns with the accessibility module support series-by-series keyboard navigation; customise point.descriptionFormat to include point.percentage and point.total. ECharts stacked bars can enable aria and decal (patterns) in one option. Chart.js stacked bars need custom keyboard handling and a table; its stacked: true scale option is purely visual.
// ECharts: patterns and generated description
option = { aria: { enabled: true, decal: { show: true } }, series: series.map(s => ({ ...s, type: 'bar', stack: 'total' })) };
Worked example
Permalink to "Worked example"A finance dashboard’s “Spend by department and category” stacked chart had six categories in close shades of teal. An accessibility review found that the Travel and Training segments were indistinguishable in greyscale and that screen readers read “image”. The team reduced visible categories to four (with “Other”), added white separators and two patterns, implemented two-dimensional arrow navigation with share-of-total announcements, and added a table with a Total column. The summary now leads with the total and the largest category for each department.
Writing the summary
Permalink to "Writing the summary"Stacked bars answer two questions, so the summary needs two parts: the trend in totals (“Revenue grew from 4.1 M in Q1 to 5.4 M in Q3”) and the composition finding (“Software is the largest line every quarter; Services grew fastest, from 0.8 M to 1.1 M”). If one series drives the change in totals, say so — that is usually the insight the chart exists to show. Keep it to two sentences; detailed values belong in the table.
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Encode 2D navigation, announcements with shares, separators and pattern fills in a StackedBarChart component, with a maximum series count that triggers a warning or a switch to grouped bars.
FAQ
Permalink to "FAQ"Should Up move up the stack visually?
Yes. Up should move to the segment above, which matches what sighted keyboard users see.
Is a table with a total column enough on its own?
It is a complete equivalent. The keyboard navigation helps users who want to explore the chart in place.
How many series can be stacked accessibly?
Four or five at most. Beyond that, show a table or split into small multiples.
Should segments be labelled with values?
Label the largest segments directly if space allows; the rest are available on focus and in the table.
How do I announce a 100% stacked chart?
Lead with the share (“57% of Q2”) and still give the absolute value and total.
Should the legend be interactive?
If clicking a legend item hides a series, it must be a button with pressed state, reachable by keyboard. See the legend toggle guidance.
How do I handle a series that is zero in some bars?
Skip zero-height segments in navigation, or announce them explicitly (“Services: none”). Skipping is less verbose; announcing keeps Up and Down predictable. Choose one and keep it consistent.
Related
Permalink to "Related"- Accessible pie and donut charts — single parts-of-a-whole
- Canvas bar chart keyboard navigation — the 1D version
- Accessible chart legends as toggle filters — hiding series