Accessible SVG Headings
We recently had a chance to investigate an issue around heading elements missing accessible names. This is particularly problematic for <h1/>
elements, which designate the most important heading on a page. The common thread of this problem was our use and misuse of SVG (Scalable Vector Graphics).
Here’s what we learned:
Sometimes a logo is just a logo
SVG is a great fit for logos. Logos are not necessarily a great fit for headings. Before diving into how we could associate SVG headings with accessible names, we had to step back and examine why we chose the SVG as heading in the first place. This allowed us to identify a few pages where we could use a more descriptive text option. A visible text heading is always preferable to an SVG heading.
Test your assumptions
For SVG headings that definitely needed to stay headings, we wanted to find a reliable approach to giving that SVG an accessible name. A developer Slack thread revealed that we had various approaches to consider:
- Visually hidden styling on text
- SVG
<title>
element aria-label
attributealt
attribute
Our test case was a simple page scaffolded with create-react-app
, using SVGR:
import { ReactComponent as Logo } from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<h1>Testing SVG labels</h1>
<h2 data-testid="1">
<span className="visually-hidden">Pattern 1</span>
<Logo aria-hidden="true" />
</h2>
<h2 data-testid="2">
<Logo role="img" title="Pattern 2" />
</h2>
<h2 data-testid="3">
<Logo role="img" aria-label="Pattern 3" />
</h2>
<h2 data-testid="4">
<Logo role="img" alt="Pattern 4" />
</h2>
</header>
</div>
);
}
export default App;
We checked the page against several browsers and MacOS's VoiceOver screenreader. We also tested WebAIM's WAVE extension and jest-dom's .toHaveAccessibleName()
test assertion. Results were as follows:
Visually hidden styling:
- Firefox Voiceover: Pass
- Chrome Voiceover: Pass
- Safari Voiceover: Pass
- WAVE: Pass
- Jest Accessible Name Assertion: Pass
<title/>
tag:
- Firefox Voiceover: Pass (but not recognized as heading title in accessibility dev tools)
- Chrome Voiceover: Pass
- Safari Voiceover: Pass
- WAVE: Pass
- Jest Accessible Name Assertion: Fail
aria-label
attribute:
- Firefox Voiceover: Pass (heading title shown with warning icon in accessibility dev tools)
- Chrome Voiceover: Pass
- Safari Voiceover: Pass
- WAVE: Fail
- Jest Accessible Name Assertion: Pass
alt
attribute
- Firefox Voiceover: Fail
- Chrome Voiceover: Fail
- Safari Voiceover: Pass
- WAVE: Fail
- Jest Accessible Name Assertion: Fail
We discussed the findings at our next developer meeting. The alt
attribute was quickly discarded as it often wasn’t identified as a heading name. The <title/>
element and aria-label
attribute introduced just enough doubt that we decided to err on the side of caution and use visually hidden styling, which offered the most consistent results.
Do something about it
Honestly, we could have debated this for a week. But we wanted to come out of the developer meeting with an actionable standard, so we’ve placed the .visually-hidden
rule on a Notion page to document our current approach to SVG headings (with a reminder to confirm you really, truly, 100% need an SVG heading). It’s not carved in stone and it’s certainly not perfect. But it’s better than what we were doing before and it’s important to celebrate small wins.