Search topics
Chart tooltips a keyboard can't open
A bar chart's counts appear only in the tooltip Chart.js draws while the pointer is on a bar, and the canvas never takes focus, so no key brings one up.
The problem
Keyboard users can't read the chart's counts, which appear only in a tooltip while a pointer is on a bar.
A shop's dashboard shows last week's orders per day as a bar chart. The bars have no numbers on them: to read Saturday's count, you point at Saturday's bar, and a small dark tooltip opens over it, "Sat" and "Orders: 31". Move off the bar and it closes. Chart.js does this by default, with no code from the shop.
Put the mouse aside and press Tab, and focus goes from whatever is above the card straight to "View all orders" under the chart. Nothing on the chart takes focus, and no key opens a tooltip, so the counts never appear. A keyboard user can see that Saturday was the busiest day, but not that it had 31 orders, or whether Tuesday had 15 or 14; the gridlines every 5 orders are all they have to estimate from.
The chart does have a text alternative: its name holds every day's count, as the first fix in charts whose numbers a screen reader can't read writes it. A screen reader user hears the counts. A sighted keyboard user sees none of that text.
WCAG 2.1.1 Keyboard asks that everything a page does with a pointer can also be done with a keyboard. Showing a bar's count is something this chart does, and only a pointer gets it to. Chart.js paints the tooltip onto the canvas, and opens it from the browser events in its events option, which by default are mousemove, mouseout, click, touchstart and touchmove, all of them from a pointer or a finger. Its default interaction opens the tooltip for the bar under the pointer only. A canvas takes no focus by default, and the chart listens for no key, so there is nothing a keyboard can do to it. That is failure F54: a function, here showing a value, wired only to pointer-specific event handlers.
The tooltip being the only place the count is shown is what makes it a failure. A tooltip that repeats a number already printed on the bar, or in a table beside the chart, adds nothing a keyboard user is missing.
Exact numbers are what people open a dashboard for: Saturday's 31 against Friday's 24 is the difference between a good weekend and a busy one. A touch screen still works, because Chart.js listens to touches; it is the keyboard, and the tools that work through it, that get nothing.
1 of 10 groups affected
- No vision (not affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Limited manipulation Keyboard and switch users press Tab past the chart and land on the link under it, and no key brings a tooltip up, so each day's count is left to be guessed from the gridlines. Voice control users can only get one by steering a pointer onto a bar, with a mouse grid or by voice, one bar at a time.
The tooltip is pixels painted on the canvas while a pointer is over it, so there is nothing in the page for a checker to find. axe sees a canvas with role="img" and a name and passes it; it doesn't hover over anything. This audit raises a lead for every chart a library drew that holds nothing that takes focus, for a person to point at its bars and then try the same with the keyboard; it can't tell, from the page, whether a tooltip holds anything that isn't printed elsewhere. An AI assistant asked for a bar chart with tooltips may reach for the library's defaults, which give exactly this Before.
Try it
Find Saturday’s count with the mouse, then try it with only the keyboard.
Point at Saturday’s bar and read the tooltip. Then put the mouse aside and press Tab: focus goes past the chart to “View all orders”, and no count is shown.
Apply a fix: the counts are printed on the bars, or Tab stops on the chart and the Left and Right arrow keys move the tooltip, with Escape to close it.
Broken Can you fix this?
Orders per day
The fix
Show every count without needing a pointer.
<section class="card"> <h2>Orders per day</h2> <p>Last week</p> <canvas id="orders" role="img" aria-label="…"></canvas> <a href="/orders">View all orders</a></section>What is wrong2 points
- The counts are drawn only in the tooltip, and the tooltip opens only while a pointer is on a bar.
- The canvas takes no focus, so Tab goes past the chart, and no key opens a tooltip.
The counts printed on the bars
<section class="card"> <h2>Orders per day</h2> <p>Last week</p> <canvas id="orders" role="img" aria-label="…"></canvas> <a href="/orders">View all orders</a></section>How it is fixed3 steps
- Add chartjs-plugin-datalabels to this chart, in its
pluginslist, so each bar's value is painted with it. - Set
anchor: 'end'andalign: 'end', so each count sits just above the top of its bar, where the eye already looks. - Paint the counts in
#1f2937, 14.68:1 against the white card, and give the chart 20 pixels oflayout.paddingat the top, so Saturday's 31 isn't cut off above the tallest bar.
This fixes the Before and passes 2.1.1: no count is left only in the tooltip, so there is nothing a keyboard has to open. The tooltip stays, for anyone pointing at a bar.
Both pass. Printing the counts is a plugin and four options, and it serves everyone at once: a keyboard user, someone magnifying the page, and anyone who never thinks to hover. It suits a chart with a handful of bars, like this one, and stops working when the labels crowd each other, with dozens of bars or several series side by side. The arrow keys keep the drawing as it was and work for any number of points, at the cost of a script, a line of instructions, and a role that has to be right; choose them for a dense chart, or one where printed numbers would bury the shape. If the chart already has the table of counts that charts whose numbers a screen reader can't read puts beside it, every value is already in text a keyboard reaches, and the tooltip is no longer the only place for them.
Why role="application" is right here, and rarely elsewhere. application tells a screen reader to give the keys to the page, which takes its reading keys away. That is wrong for content that has something to read, and it is why the role is almost never the answer. A canvas has nothing in it to read: every key it needs, it handles, and every count it shows, it writes to the status line. Recharts 3 does the same by default, with role="application" and tabindex="0" on the chart, the arrow keys moving its tooltip, and the tooltip a live region; Recharts 2 left this off unless accessibilityLayer was set.
The tooltip on focus is content on focus. A tooltip that opens when the chart gets focus must close on Escape without focus moving, and stay open until it is dismissed or focus leaves, as WCAG 1.4.13 asks of content on hover or focus. How a tooltip should behave when it vanishes on approach or covers what is under it is covered in tooltips that vanish on approach and can't be dismissed.
Keep the status line short. It is read after every press, so give it the day and the count, and nothing that repeats the chart's name.
Other libraries. Highcharts' accessibility module adds keyboard navigation through the points. A chart drawn as SVG by hand, such as the D3 Graph Gallery's tooltip example, which shows a tooltip div on mouseover and hides it on mouseout, fails the same way, and the same two fixes apply: a text element over each mark, or one Tab stop whose arrow keys move between them.
Bars that filter the page or drill down when clicked are another form of the same failure, which no guide covers yet. A legend that hides a series only on click is covered in chart legends a keyboard can't toggle.
Design ideas
A chart can show its numbers without a hover.
A figure that follows both the pointer and the keys, beside a popular look that shows the counts only while the pointer is on the chart. Hover each sample, then try it with Tab and the arrow keys.
In the design file
- Never leave a count only in what appears on hover; give the keyboard a way to bring up every one, or print them on the chart.
- If the chart answers to the pointer, also draw the same moment reached with the keyboard, with the chart’s focus ring and the bar it is on.
- Write which keys move from day to day beside the chart, such as the left and right arrow keys.
- Set the counts at 4.5:1 or more against the chart’s background; the dark ink here is 14.76:1 on white.
Passes
Orders per day, last week
20 ordersSunday
Left and right arrow keys move between days.
A big figure that follows the pointer and the keys Above the bars, “20 orders, Sunday” shows the latest day at rest. Point at a bar, tap it, or Tab to the chart and press the arrow keys, and the figure changes to that day, the bar turning a darker blue. Nothing floats over the bars, and the keyboard reaches every day.
Fails
Orders per day, last week
Numbers that appear on hoverWCAG 2.1.1 At rest the chart is only bars; move the pointer onto it and every count fades in above its bar. It looks tidy, but Tab goes straight past, and nothing brings the numbers up from the keyboard.Rule of thumb: Make a gesture a shortcut, never the only way
Verify the fix
4 checks, no mouse.
Test with: Keyboard, Screen reader
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat whenever the chart component or its library is updated.
How screen readers handle role="status", aria-describedby and role="application" tabindex="0"
Limits
This is a starting pattern, not a guarantee for every chart. Chart is Chart.js, setActiveElements its own way to open a tooltip from code, and chartjs-plugin-datalabels a separate package from the Chart.js project; another library's equivalents go in their place. Screen readers differ in how they treat role="application", and some may keep the arrow keys for reading; test the complete dashboard with a keyboard and your target assistive technology.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
References
- WCAG Understanding: keyboard (opens in a new tab)
- WCAG Technique F54 (opens in a new tab)
- WCAG Understanding: content-on-hover-or-focus (opens in a new tab)
- w3.org: wai-aria-1.2 (opens in a new tab)
- chartjs.org: tooltip (opens in a new tab)
- chartjs.org: interactions (opens in a new tab)
- chartjs.org: programmatic-events (opens in a new tab)
- chartjs-plugin-datalabels.netlify.app: positioning (opens in a new tab)
- github.com: v3.0.0 (opens in a new tab)
EaseWeb service
Fixed it? Keep it from coming back.
I turn guides like this one into rules your coding agents follow and checks your CI runs. Then I retest by hand with a keyboard and screen readers, periodically.