Accessible SVGs in React

August 23, 2019 • 2 min read


tl;dr add a <title> tag to you SVGs to improve accessiblity. If you need general browser support (Safari in particular) read on…


With <title> tags you can let screenreader users know what a SVG represents. However, browser support is not quite there yet. So for instance VoiceOver on Mac + Safari does not read titles to the user.

You can use some aria magic to fix this:

export const PlayIcon = () => (
  <svg
    aria-labelledby=“play-id”
    role="img"
    viewBox="0 0 24 24"
    xmlns="http://www.w3.org/2000/svg"
  >
    <title id="play-id">play</title>
    <path d="M8 5v14l11-7z" />
    <path d="M0 0h24v24H0z" fill="none" />
  </svg>
);

The challenge with this approach is that html ids have to be unique. And when you have a lot of PlayIcons on your screen, this can get problematic. A way to solve this is to generate some kind of unique id for every icon in the DOM, but to me this feels like engineering overkill.

Hiding SVGs from screenreaders

Sometimes you want to hide the SVGs entirely from screenreaders, for instance when you have a button that already has a label and the icon is just a visual decoration that does not add any indispensable information. In that case you can use aria-hidden to make sure it’s not picked up by screenreaders (just as a empty string as alt tag (alt="") hides images).

In general I also want leave the decision whether or not an SVG should have an accessible title to the parent component. So I ended up with the following React component:

export const PlayIcon = ({ accessibleTitle }) => (
  <svg
    aria-labelledby={accessibleTitle ? 'playid' : null}
    aria-hidden={accessibleTitle ? false : true}
    role="img"
    viewBox="0 0 24 24"
    xmlns="http://www.w3.org/2000/svg"
  >
    {accessibleTitle && <title id="playid">play</title>}
    <path d="M8 5v14l11-7z" />
    <path d="M0 0h24v24H0z" fill="none" />
  </svg>
)

And you would use it like so:

const App = () => (
  <button>
    <PlayIcon accessibleTitle="play" />
  </button>
)

Here’s a CodeSandbox: https://codesandbox.io/embed/accessible-svgs-4egiz

And here’s the website

Browser support

Browser support for using only a <title> tag is getting better. As of 25th August, 2019 it look like this:

  • VoiceOver + Chrome: WORKS ✅
  • VoiceOver + Chromium (new Edge + Brave): WORKS ✅
  • VoiceOver + Safari 12 + 13: DOES NOT ❌
  • VoiceOver + Firefox: (how do I get this to work, somebody know?)
  • NVDA + Chrome: WORKS ✅
  • NVDA + Edge: WORKS ✅
  • NVDA + Firefox: DOES NOT ❌

Note that on Firefox the solution with aria-labelledby causes NVDA to read the icon name twice.

Got any questions, found a mistake? You can find me on Twitter as @vnglst. You can also discuss the article on TwitterSuggest changes on Gitlab


Koen van Gilst

Koen van Gilst

Personal blog by Koen van Gilst. JavaScript developer, M.A. in Philosophy, former translator.