Repository navigation
modebar tools are not accessible #6645
Copy link
Copy link
Closed
Labels
Description
I'm currently working in a small team on a Django website that has an embedded Plotly Dash. We're working through accessibility issues raised by pa11y-ci and have identified a few that are arising from within Plotly.js itself.
Here's the minimal example I set up based on the "getting started" example code on the Plotly.js website:
<!DOCTYPE html>
<html lang=en>
<head>
<title>Plotly.js Minimal Working Example</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div id="tester" style="width:600px;height:250px;"></div>
<script>
TESTER = document.getElementById('tester');
Plotly.newPlot( TESTER, [{
x: [1, 2, 3, 4, 5],
y: [1, 2, 4, 8, 16] }]);
</script>
</body>
</html>I served this using a simple Python server:
$ python3 -m http.server 8500 --bind 127.0.0.1 &
and run pa11y-ci with this configuration:
$ cat .pa11yci
{
"defaults": {
"runners": ["axe", "htmlcs"],
"chromeLaunchConfig": {
"args": ["--no-sandbox"]
}
},
"urls": [
"http://127.0.0.1:8500/plotly-mwe.html"
]
}
which produces a number of issues (full log collapsed below), several of which look like this:
$ npx pa11y-ci
Running Pa11y on 1 URLs:
127.0.0.1 - - [15/Jun/2023 15:40:27] "GET /plotly-mwe.html HTTP/1.1" 200 -
> http://127.0.0.1:8500/plotly-mwe.html - 22 errors
Errors in http://127.0.0.1:8500/plotly-mwe.html:
...
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(1) > a)
<a rel="tooltip" class="modebar-btn" data-title="Download plot as a png" data-toggle="false" data-gravity="n"><svg viewBox="0 0 1000 1000" cl...</a>
...
✘ 0/1 URLs passed
There's one of these for each of the tooltips, presumably because they have neither id nor name attributes.
I'm not sure what the most appopriate id or name attribute would be.
Full error log
$ npx pa11y-ci
Running Pa11y on 1 URLs:
127.0.0.1 - - [15/Jun/2023 15:40:27] "GET /plotly-mwe.html HTTP/1.1" 200 -
> http://127.0.0.1:8500/plotly-mwe.html - 22 errors
Errors in http://127.0.0.1:8500/plotly-mwe.html:
• Page must have means to bypass repeated blocks (https://dequeuniversity.com/rules/axe/4.7/bypass?application=axeAPI)
(html)
<html lang="en"><head> <title>Plotly.js Min...</html>
• Elements must meet minimum color contrast ratio thresholds (https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI)
(#tester > div > div > svg:nth-child(1) > g:nth-child(5) > g > g:nth-child(15) > g:nth-child(1) > text)
<text text-anchor="middle" x="0" y="183" data-unformatted="1" data-math="N" transform="translate(105.75,0)" style="font-family: "Open Sans", verdana, arial, sans-serif; font-size: 12px; fill: rgb(68, 68, 68); fill-opacity:
1; white-space: p...
• Elements must meet minimum color contrast ratio thresholds (https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI)
(#tester > div > div > svg:nth-child(1) > g:nth-child(5) > g > g:nth-child(15) > g:nth-child(2) > text)
<text text-anchor="middle" x="0" y="183" data-unformatted="2" data-math="N" transform="translate(202.87,0)" style="font-family: "Open Sans", verdana, arial, sans-serif; font-size: 12px; fill: rgb(68, 68, 68); fill-opacity:
1; white-space: p...
• Elements must meet minimum color contrast ratio thresholds (https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI)
(#tester > div > div > svg:nth-child(1) > g:nth-child(5) > g > g:nth-child(15) > g:nth-child(3) > text)
<text text-anchor="middle" x="0" y="183" data-unformatted="3" data-math="N" transform="translate(300,0)" style="font-family: "Open Sans", verdana, arial, sans-serif; font-size: 12px; fill: rgb(68, 68, 68); fill-opacity: 1;
white-space: pre;...
• Elements must meet minimum color contrast ratio thresholds (https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI)
(#tester > div > div > svg:nth-child(1) > g:nth-child(5) > g > g:nth-child(15) > g:nth-child(4) > text)
<text text-anchor="middle" x="0" y="183" data-unformatted="4" data-math="N" transform="translate(397.12,0)" style="font-family: "Open Sans", verdana, arial, sans-serif; font-size: 12px; fill: rgb(68, 68, 68); fill-opacity:
1; white-space: p...
• Elements must meet minimum color contrast ratio thresholds (https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI)
(#tester > div > div > svg:nth-child(1) > g:nth-child(5) > g > g:nth-child(15) > g:nth-child(5) > text)
<text text-anchor="middle" x="0" y="183" data-unformatted="5" data-math="N" transform="translate(494.25,0)" style="font-family: "Open Sans", verdana, arial, sans-serif; font-size: 12px; fill: rgb(68, 68, 68); fill-opacity:
1; white-space: p...
• Elements must meet minimum color contrast ratio thresholds (https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI)
(#tester > div > div > svg:nth-child(1) > g:nth-child(5) > g > g:nth-child(16) > g:nth-child(1) > text)
<text text-anchor="end" x="79" y="4.199999999999999" data-unformatted="0" data-math="N" transform="translate(0,166.45)" style="font-family: "Open Sans", verdana, arial, sans-serif; font-size: 12px; fill: rgb(68, 68, 68);
fill-opacity: 1; wh...
• Elements must meet minimum color contrast ratio thresholds (https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI)
(#tester > div > div > svg:nth-child(1) > g:nth-child(5) > g > g:nth-child(16) > g:nth-child(2) > text)
<text text-anchor="end" x="79" y="4.199999999999999" data-unformatted="5" data-math="N" transform="translate(0,147.95)" style="font-family: "Open Sans", verdana, arial, sans-serif; font-size: 12px; fill: rgb(68, 68, 68);
fill-opacity: 1; wh...
• Elements must meet minimum color contrast ratio thresholds (https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI)
(#tester > div > div > svg:nth-child(1) > g:nth-child(5) > g > g:nth-child(16) > g:nth-child(3) > text)
<text text-anchor="end" x="79" y="4.199999999999999" data-unformatted="10" data-math="N" transform="translate(0,129.45)" style="font-family: "Open Sans", verdana, arial, sans-serif; font-size: 12px; fill: rgb(68, 68, 68);
fill-opacity: 1; w...
• Elements must meet minimum color contrast ratio thresholds (https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI)
(#tester > div > div > svg:nth-child(1) > g:nth-child(5) > g > g:nth-child(16) > g:nth-child(4) > text)
<text text-anchor="end" x="79" y="4.199999999999999" data-unformatted="15" data-math="N" transform="translate(0,110.95)" style="font-family: "Open Sans", verdana, arial, sans-serif; font-size: 12px; fill: rgb(68, 68, 68);
fill-opacity: 1; w...
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(1) > a)
<a rel="tooltip" class="modebar-btn" data-title="Download plot as a png" data-toggle="false" data-gravity="n"><svg viewBox="0 0 1000 1000" cl...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(2) > a:nth-child(1))
<a rel="tooltip" class="modebar-btn active" data-title="Zoom" data-attr="dragmode" data-val="zoom" data-toggle="false" data-gravity="n"><svg viewBox="0 0 1000 1000" cl...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(2) > a:nth-child(2))
<a rel="tooltip" class="modebar-btn" data-title="Pan" data-attr="dragmode" data-val="pan" data-toggle="false" data-gravity="n"><svg viewBox="0 0 1000 1000" cl...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(2) > a:nth-child(3))
<a rel="tooltip" class="modebar-btn" data-title="Box Select" data-attr="dragmode" data-val="select" data-toggle="false" data-gravity="n"><svg viewBox="0 0 1000 1000" cl...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(2) > a:nth-child(4))
<a rel="tooltip" class="modebar-btn" data-title="Lasso Select" data-attr="dragmode" data-val="lasso" data-toggle="false" data-gravity="n"><svg viewBox="0 0 1031 1000" cl...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(3) > a:nth-child(1))
<a rel="tooltip" class="modebar-btn" data-title="Zoom in" data-attr="zoom" data-val="in" data-toggle="false" data-gravity="n"><svg viewBox="0 0 875 1000" cla...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(3) > a:nth-child(2))
<a rel="tooltip" class="modebar-btn" data-title="Zoom out" data-attr="zoom" data-val="out" data-toggle="false" data-gravity="n"><svg viewBox="0 0 875 1000" cla...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(3) > a:nth-child(3))
<a rel="tooltip" class="modebar-btn" data-title="Autoscale" data-attr="zoom" data-val="auto" data-toggle="false" data-gravity="n"><svg viewBox="0 0 1000 1000" cl...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(3) > a:nth-child(4))
<a rel="tooltip" class="modebar-btn" data-title="Reset axes" data-attr="zoom" data-val="reset" data-toggle="false" data-gravity="n"><svg viewBox="0 0 928.6 1000" c...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(4) > a:nth-child(1))
<a rel="tooltip" class="modebar-btn" data-title="Toggle Spike Lines" data-attr="_cartesianSpikesEnabled" data-val="on" data-toggle="false" data-gravity="n"><svg viewBox="0 0 1000 1000" cl...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(4) > a:nth-child(2))
<a rel="tooltip" class="modebar-btn" data-title="Show closest data on hover" data-attr="hovermode" data-val="closest" data-toggle="false" data-gravity="ne"><svg viewBox="0 0 1500 1000" cl...</a>
• Anchor element found with no link content and no name and/or ID attribute.
(#modebar-c74f48 > div:nth-child(4) > a:nth-child(3))
<a rel="tooltip" class="modebar-btn active" data-title="Compare data on hover" data-attr="hovermode" data-val="x" data-toggle="false" data-gravity="ne"><svg viewBox="0 0 1125 1000" cl...</a>
✘ 0/1 URLs passed
Reactions are currently unavailable
Activity
alexcjohnson commentedon Jun 22, 2023
Thanks @warrickball - do you have a reference for how exactly
idand / ornameattributes improve accessibility? These anchor elements are the actual modebar buttons,rel="tooltip"looks to me like an obsolete artifact we should remove (the buttons have tooltips added by CSS based on thedata-titleattribute).The buttons are created here:
plotly.js/src/components/modebar/modebar.js
Line 140 in ab5e16a
based on config items in buttons.js
Obvious
namewould beconfig.name, oridmaybeconfig.name + '-' + this.graphInfo._fullLayout._uidwarrickball commentedon Jun 23, 2023
I want to first disclaim that I am absolutely not an expert on any of this. My understanding is that this error is associated with principle 4.1.2 of the Web Content Accessibility Guidelines (WCAG) standards (v2.0). Some searching online led me to this advice, which I think suggests using something like the current
data-titlefor thetitlefield.I'll see if I can make that change myself and run
pa11y-cion it. If that passes, I'm happy to open a PR. I don't know much JavaScript but I'm guessing I could use the same logical block (in the code you linked) that setsdata-titleto also settitle.warrickball commentedon Jun 23, 2023
Hmmm... Just setting the
titleattribute didn't work and I wasn't sure about generating a whole set of short names. An alternative recommended here suggests adding a<title>tag as the first child of the inline SVG element. I copied the short description fromdata-titleto such an element andpa11y-cireports no error. I'll see if I can figure out how to code this.warrickball commentedon Jun 23, 2023
I think I can see where the new code would go but I think it's beyond me to implement. This looks like the code that creates the icon:
plotly.js/src/components/modebar/modebar.js
Lines 196 to 230 in ab5e16a
My take would be to try to add an extra child
<title>...</title>at L218, where...is the same information that goes intodata-title. But I don't see that being passed down into the icon creator and am not sure how one would do that.The creator that writes bundled SVG code only seems to apply to
newplotlylogo(seesrc/fonts/ploticon.js), so I guess the solution there (and for any hardcoded SVG icons added later) is to add atitletag to the hardcodedsvgtag.[-]Accessibility of tools in modebar[/-][+]modebar tools are not accessible[/+]emilykl commentedon Aug 13, 2026
@warrickball I came across this issue while scanning older issues in the repo, and I believe it's been resolved by #7429, which (among other changes) changed the modebar buttons from
<a>elements to<button>elements, as well as addingdata-titleandaria-labelattributes.Running
npx pa11y-ciagainst https://cdn.plot.ly/plotly-4.0.0-rc.0.min.js (the latest release) following your suggested configuration shows no issues related to the modebar.I'll close this as completed; if you think that's incorrect please let us know!
warrickball commentedon Aug 17, 2026
Thanks for returning to this after all this time. I also just resurrected my old example and confirm that the checks now pass.