If you’re using SVG for more than just icons, such as structured content like a chart or diagram, especially an interactive one, then you need to take a few extra steps to make it accessible. But making an SVG accessible has been a bit of a moving target over the years, usually getting better, but sometimes getting temporarily obscured, because of changes in browser and screen reader support. We’ve specced out most of this behavior, but it doesn’t mean we caught everything, or that it’s implemented consistently or correctly across all screen readers or browsers. And some methods that worked (a bit accidentally) were unintuitive or caused validation errors.
What we want for accessibility are clear, consistent methods for making SVG accessible, that are both reliable and valid across the whole toolchain, from authoring tools to validators and checkers to browsers to screen readers.
I’m happy to report that now the mechanics of SVG accessibility have never been better. It’s more predictable, more consistent, more interoperable, more standards-conformant, and easier than ever before to make SVG elements accessible to a screen reader.
TL;DR:
For each focusable or interactive SVG element, add a tabindex
attribute, a role
attribute, and an accessible name (via an aria-label
attribute or a <title>
element).
That’s it!
Secret traps:
- Don’t add a
tabindex
attribute without an accessible name - Don’t add a
tabindex
attribute to the SVG root unless you need to - Avoid the
title
attribute (not to be confused with the<title>
element)
The long version, in which we figure out some things that don’t work, some things that used to be necessary but are no longer needed, and just how strangely screen readers can act
I was helping John Gardner from ViewPlus with a new prototype for a simpler interface to load, insert, modify, and save SVG files in an HTML file, in an accessible way. As an incidental part of this, I made a few rough SVG example files that could be loaded and modified; I didn’t pay much attention to the sample SVG files, and just reused some simple SVG accessibility techniques that had mostly worked (and been necessary) at one time. I tested it in VoiceOver on my Mac and it worked fine.
By “worked”, I mean I could use the keyboard to navigate from the HTML form controls to the inline SVG (that is, an SVG file that’s been embedded directly into the HTML code), move to each of the “activatable” shape elements (the ones that had event listeners on them, along with tabindex
attributes), hear the title of that element, activate one or more of them with the space
or enter
keys (which added a stroke to the shape), and navigate back out to the HTML controls to save the modified files. The SVG files were not the relevant part of the prototype; they were just stand-ins for the kind of files users would be loading. They wouldn’t affect the prototype, right? Wrong.
When I was demoing it with John, who uses NVDA and JAWS, he couldn’t navigate to the SVG elements. I could hear his screen reader through the phone. It kept cycling back to the last form element, a button, and repeating the name of that button. Sometimes it would get stuck, and he couldn’t navigate back to the previous form elements. He wasn’t sharing his screen, so I had no idea what was going on, and neither did he. Once in a while, he’d stumble on one of the target elements, but he couldn’t activate it.
Time to step back and debug this very odd behavior.
I don’t have a working Windows machine right now, and I wanted the advice of native screen reader users, so I took to email and Twitter. One of the joys of working in the a11y community is that people are so helpful and friendly. My friends Léonie Watson (@LeonieWatson) of TetraLogical and Jason White of ETS immediately volunteered to help, and started testing the file. And two strangers also stepped up: Kevin Ackley (@ackleyk), an Accessibility Tech Consultant from Nationwide Insurance, and Frank Elavsky (@FrankElavsky).
Kevin messaged me, and I sent him the sample app. A couple minutes later, I got a phone call. He needed to know what the expected behavior was. I walked him through it, and could hear similar problems as those John had. But with a critical difference: Kevin could see that the focus was shifting to the SVG elements, but it was still saying the accessible name from the HTML form control! And he couldn’t activate the elements! What fiendish torture was this? Had the browsers and Windows screen readers reverted to broken SVG behavior? What had 2020 done?!
For a moment, I felt despair. SVG and accessibility are two of my favorite technologies! They should work well together. They had to! I’d helped write the SVG accessiblity specs, for crying out loud! If I was struggling with this, then lots of other people had to be struggling, too. With Kevin patiently agreeing to be my remote eyes and ears and screen reader operator, I went back to basics and started testing combinations of SVG accessibility features. A furious set of permutations of tabindex
, <title>
, title
, aria-labelledby
, aria-label
, and role
followed, with Kevin and I both hacking the source code over a couple of hours. Herein I’ll recount our most salient findings, with 3 problems and 4 best practices.
Context: SVG elements need a tabindex
attribute to receive focus
In HTML, most content is wholly or partly text. You usually don’t need to do anything special to make sure the screen reader reads that text out, though you do need judicious use of tabindex
on custom controls. By contrast, most SVG content is shapes, which are not keyboard focusable by default. The only elements that are focusable by default are links. If you want a shape element to be interactive or navigable, you need a tabindex
attribute with a value of "0"
. (Historically, SVG used to have a different boolean attribute for making an element focusable, mysteriously called focusable
, but we deprecated it for integration into HTML in favor of the legacy HTML tabindex
. Alas, you should not use focusable
, even if it seems more logical.)
Every focusable element also needs an accessible name, which you can add with the native <title>
as a child of the shape element, or with an aria-label
attribute.
It’s also worth noting that the alt
attribute doesn’t work with inline or standalone SVG elements, not even with the <image>
element (the SVG analog to the HTML <img>
element). You need to use an aria-label
attribute or <title>
element to add an accessible name. You can (and should) use the alt
attribute for SVG files included in HTML via the <img>
element, of course.
Problem 1: tabindex
on the SVG root
This was a case of overengineering. Last year, I found a bug in some Windows screen reader / browser combos that you couldn’t navigate to an SVG shape element, even one with a tabindex
attribute, unless you also had a tabindex="0"
on its <svg>
root element. It was like there was a windowless wall, and the root tabindex
was the key to the door that let the screen reader inside the room. An annoying bug, but easy to work around. But it turns out, this is no longer necessary in any screen reader and browser combination we tried (VoiceOver / NVDA / JAWS and Firefox / Safari / Chrome). It just adds an unnecessary and possibly disorienting step. Don’t do this anymore, if you ever did.
This might not have been so bad in isolation, but it was devastating in combination with…
Problem 2: no accessible name on the SVG root
An “accessible name” for an element is the text the screen reader reads out when it’s presenting that element to the end user. This the text of the heading or button, or the alt-text of the image. (This is also the text label that someone using voice control uses to active a UI widget.) Without a text description, most SVG elements don’t have an accessible name.
Normally, I always add a top-level <title>
element to my SVG files. I skipped it for these throwaway sample files, and I’m glad I did. Because that gave me the chance to discover that not only was the root tabindex
unnecessary, but it also revealed some truly weird screen reader behavior. When NVDA or JAWS encounter an SVG element with tabindex="0"
and no accessible name, they get confused. Very, very confused. They start repeating the last accessible name they remember (in this case, the HTML form control). Even when they encounter a subsequent focusable SVG shape element with an accessible name, they just can’t get over the trauma of the missing accessible name, and endlessly repeat the last good accessible name they spoke, for any SVG element afterward, whether navigating forward or backwards in DOM order.
And here’s where it gets truly odd. This actually seems to affect the navigation behavior, at least in JAWS. You can no longer reliably navigate back out of the SVG root. (Disclaimer: I didn’t see this for myself, I only heard it, and we were testing behavior fast. It might be that the navigation itself wasn’t affected, but only what was read out. Either way, tricky behavior you want to avoid.)
I’m assuming this is some sort of missing stack reference issue in the screen readers. Don’t fall prey to this one, because it will screw up all your SVG navigation. If you add a tabindex
attribute to any SVG element, make sure you give it an accessible name, too.
One we sorted this out, the navigation behavior was smooth and consistent. And Kevin could “activate” the SVG shape element (adding a stroke via JavaScript event handler)… in NVDA. But not in JAWS. If it didn’t work in JAWS, it wasn’t really ready for deployment on the web.
Problem 3: missing role
attribute
A little tinkering revealed the problem. I hadn’t added a role
attribute to the SVG shape elements. Again, this turned out to be a happy accident (normally, I do add a role
and often a aria-roledescription
), since it illustrated the necessity of using it.
The role
attribute is a feature of of the ARIA specification, which adds or changes the semantics of the element it’s used on. With a role
, you can tell screen readers that a <div>
is actually a button, for example, and that it should have the same behaviors as a button, such as being focusable and activatable.
Without a role
attribute on the SVG shape element, in this case with the value “button”, JAWS would not activate the element, even though it had an event listener on it.
I will be honest: I’m not sure if this is the correct behavior per the specs or not. Either way, I personally think it’s the wrong behavior. This isn’t required for VoiceOver or for NVDA, and I think JAWS should change its behavior here. Consistency between user agents is incredibly important. But this is a pretty easy workaround, and a role is typically useful and adds semantics, so to make sure your interactive SVG elements are activatable, add a role
attribute.
As an aside, Kevin pointed out that a few years ago, the way to make SVGs consistently read by screen readers was to follow Léonie’s advice from the article below, which includes using role="img"
on the SVG root. Some people leveraged this by removing the role for SVG icons they wanted to be ignored. Even if a <title>
were available, if role="img"
were not present, the title would not be read. Recently, however, at least some screen readers have changed this behavior, so this hide-the-SVG hack no longer works. Avoid it. Either provide a meaningful accessible name to make it visible to screen readers, or leave off the accessible name to hide it.
I haven’t yet tested if you need to add specific roles (like "button"
) to make the element activatable, so proceed with caution.
Having solved all the major problems, we next turned to some quick best practices, to distill the minimum of what is necessary, and what is unneeded.
Best Practice 1: no aria-labelledby
attribute required
In Léonie Watson’s excellent 2014 article Tips for Creating Accessible SVG, she describes a belt-and-bracers technique that was often necessary at the time. Some screen readers would not recognize the content of the the native SVG <title>
element (again, not to be confused with the title
attribute inherited from HTML) as the accessible name. To overcome this, Léonie wisely recommended that you use the aria-labelledby
attribute to point to the child title, like so:
<path aria-labelledby="button_1" d="…">
<title id="button_1">Shiny red button</title>
</path>
This is effective, but frankly, tedious and verbose to maintain. And luckily, no longer necessary! We found no difference in screen reader / browser behavior between that and the simpler form:
<path d="…"> <title>Shiny red button</title> </path>
It won’t hurt to continue using the belt-and-bracers technique, but in 2020, it’s doesn’t seem necessary. In fact, pointing aria-labelledby
at the <title>
element may simply not have any effect. When Frank did some later validation testing for me (more on that below), he found a typo in one of my aria-labelledby
attribute values, so it didn’t point to an existing id
. It didn’t make a difference in the accessible name generation for that element.
Note that accessibility support for <title>
is brand new in Firefox! The Firefox 79 release notes from July 28, 2020 call out new support for exposing the <title>
and <desc>
elements to assistive technology, resolving a bug reported at least 5 years ago and noted in the great 2016 article Accessible SVGs by Heather Migliorisi (@_hmig).
However, it may be better not to use the <title>
element at all. Like the HTML title
attribute, modern browsers treat the content of the <title>
element as a tooltip, popping up a little infobox after a short delay when you hover over the element. This is often not desirable, and inconsistent since it doesn’t really work on mobiles.
Best Practice 2: use aria-label
attribute instead of <title>
element
When do you not want the tooltip provided by the <title>
element? When you’re providing more complex custom tooltips, especially ones that show immediately rather than requiring a delay. When you don’t want the title of the parent element to be shown for a child element without a <title>
element. When it causes visual clutter or distraction on the “empty” space of a chart (imagine having the HTML’s <title>
content pop up every time you rest your cursor on the page background).
But you definitely want to add an accessible name. You can use the aria-label
attribute for this. It provides an accessible name for that element to the screen reader, without prompting the browser to make a popup. If you want to provide a popup on hover and focus (for consistency), it’s easy to create one using JavaScript, taking the content from the aria-label
or data-*
attributes.
SVG 1.0 had some great aspirational accessibility features, including text that’s actually text (not just an image of text), and the <title>
the <desc>
(description) elements. Having <title>
as an element, rather than as an attribute, provides a number of advantages, including structured markup content and providing multiple alternative-language versions. These features, their motivations, and some speculations are documented in the W3C Accessibility Features of SVG note by Chaals McCathieNevile (@chaals) and Marja-Riitta Koivunen. But when we conflated <title>
with a tooltip, which seemed logical at the time, we muddied the waters. I think this is a decision that should be revisited, and that <title>
popups should be removed from browsers, possibly replaced with a hint semantic that works better with keyboards, touchscreens, and assistive technology.
Best Practice 3: avoid title
attribute
Many accessibility professionals warn against using the title
attribute in HTML. Scott O’Hara has a well-researched and exhaustive article on the varied support for the title
attribute that’s worth reading.
In our tests of screen reader and browser behavior, the title
attribute on an SVG element acted no differently than the aria-label
attribute. Unlike in HTML, the title
attribute in SVG doesn’t even produce a popup (while the <title>
element does). The title
attribute worked perfectly well in all screen readers to generate an accessible name. For better or worse, we don’t have the same complaints in SVG against the title
attribute as described in Scott’s article.
However, in Frank’s validation testing, the title
attribute was flagged as invalid in the W3C Validator. For the record, I don’t think that’s correct. I see this as more of an oversight in standards or a mistake in the Validator than as a problem with the SVG file. The title
attribute, being a global attribute in HTML, should be allowed on any SVG element, too. That said, I still recommend against using it. If it were the only thing that worked, to be blunt, I’d use it whether it validated or not, but luckily, it’s not the only thing that works. The behavior is inconsistent with HTML, it’s unnecessary since it’s no better than using the aria-label
attribute, and most importantly, it fails validation rightly or wrongly. Validation errors are a red flag that will be thrown in any accessibility testing or audit, and it will cause hassles without providing value.
In short, don’t use the title
attribute in SVG (except perhaps on links which don’t have text content, and even there, you should probably use another accessible name mechanism).
Best Practice 4: validate and test
After Kevin and I wrapped up debugging the problem two or three hours later, I checked my email and saw that both Léonie and Jason had also had similar experiences and come to similar conclusions. Some simple errors in my SVG files caused utter chaos in screen readers, easily corrected but a hassle for screen reader users (and SVG authors like yours truly).
Afterwards, I had a conversation with Frank, who’s done a lot of SVG accessibility testing, too. Both while working at Visa and outside of work, Frank needs to make certain he is following all the regulations and providing an accessible experience, so he has a rigorous testing regime, including using the W3C Validator, aXe, and the Accessibility Insights extension for Chrome. Visa also has open-sourced their accessibility requirements for the community to learn from their tests, procedures, and recommended tools. I’m not specifically endorsing any of these tools; there are loads of other great accessibility tools out there, including Compliance Sheriff and Tenon to name just a couple, but find the tools that work best for your workflow, and use them.
Frank generously validated my code, both in situ (the injected inline SVG) and as the raw SVG files. He found the errors mentioned before (the typo in the aria-labelledby
value and the validity errors for the title
attribute on SVG elements), and wrote me a concise report via email. His testing came after we’d already solved the problem. I can’t say for certain that it would have pointed me to the solution that Kevin and I arrived at (and which Léonie and Jason verified), but it would definitely have pointed out some of my errors.
Now it’s your turn!
And that’s the kicker. We suffered so you don’t have to. I got lazy, and it caused me stress and lost time, even if I talked with and met some great people through it. If I’d tested myself in NVDA and JAWS, as well as using validation and checking tools, I may not have introduced these problems in the first place. Because I built accessibility in my code from the start (even if it wasn’t perfect), it only took me a short time to recover from the problems we’d found; it would have taken much more time to fix retroactively if I’d been further on.
Please learn from my mistakes. Follow these simple up-to-date SVG accessibility best practices, avoid the nasty problems, don’t get lazy, and test and validate with accessibility tools and real-world screen readers and browsers. SVG accessibility can be easy!
Once again, I want to profoundly thank Kevin Ackley, Léonie Watson, Jason White, and Frank Elavsky for their generosity, friendliness, and technical expertise in helping me diagnose and solve these issues. Without them, it would have been much more frustrating and much less fun.
Thanks so much for the in depth testing. One oddity I noticed when inspecting SVGs in Firefox was that they seem to assign role=”diagram” to the root by default (which may explain why role=”img” is required). I was quite confused by Firefox’s behavior, since diagram isn’t a valid ARIA role?
For folks doing this sort of testing in the future, please check out and give feedback on https://assistivlabs.com/ — you can virtually test NVDA, JAWS, and more from your web browser, including jumping between legacy and beta AT/browser versions, which can be especially handy for finding the exact version a certain compatibility technique is no longer required.
Hey, Weston, Assistiv Labs sounds like a really great idea. While debugging this, I wished for pretty much that exact thing, a headless AT/browser testing service, so I’m really glad you’re doing it.
That auto-inserted lacuna role of “diagram” for Firefox really is odd! I hadn’t noticed that. If they wanted to infer a value, why wouldn’t they use one of the valid ones from the WAI-ARIA Graphics Module?
But is a role on the SVG root required? Screen readers don’t seem to need it, and it didn’t get flagged in our validation. If I’m wrong about this, I’ll update the post to reflect that requirement.
In my testing, role=”img” is still required on the SVG root to have non-interactive SVG be announced in NVDA + Firefox and iOS VoiceOver + Safari (my test case used for an accessible label). And worth noting that although MacOS VoiceOver+Safari announces the SVG, it announces as a “group”, which might be confusing. Your post essentially agrees in that leaving off role=”img” no longer guarantees that the SVG will be hidden from ATs.
One thing I’m still curious about is how to handle non-interactive SVGs that should still expose details about their contents. For example, a bar chart, where you can use the AT’s virtual cursor to navigate to each bar for detailed info. Or is that the wrong pattern, and the interactive approach you’ve outlined above is more appropriate?
In Heather’s example (https://css-tricks.com/accessible-svgs/#3-embed-svg-via-object-or-iframe), role=”group” is used to expose the SVG’s inner elements to the accessibility tree, but I see on https://www.w3.org/TR/html-aria/ that “group” isn’t a permitted role for SVG (assuming that document is valid, I see it’s still in draft. My spec navigating skills need work).