Skip to content

Instantly share code, notes, and snippets.

@davidhund
Last active March 27, 2022 12:18
Show Gist options
  • Save davidhund/564331193e1085208d7e to your computer and use it in GitHub Desktop.
Save davidhund/564331193e1085208d7e to your computer and use it in GitHub Desktop.
Figuring out the most robust, accessible markup for SVG Sprite Icons

(as a reply to: https://css-tricks.com/svg-use-with-external-reference-take-2/)

While I love SVG (sprites) there are a lot of issues to take into account.

Advocating The Most Accessible Markup

UPDATE: you should take a look at https://dl.dropboxusercontent.com/u/145744/accessible-svg-icon/test.html which seems a simpler and more robust approach

Another thing: since people copy-paste our code examples it would be great if we could advocate the most robust and accessible markup IMO. I am no a11y expert, but in my understanding we could/should take some extra steps to make out SVG sprite icons more accessible.

Below my current thinking, I would love an a11y expert's advice on this!

‘Presentational’ or ‘Content’ icons?

First: make a distinction between 'presentational' and 'content' icons. Just as we would with img and their alt attributes.

For all SVG Icon sprites (in sprite.svg):

  • Add a title element child to the sprite's symbol
  • Add a desc element child to the sprite's symbol
<symbol id="left-arrow">
	<title>Back</title>
	<desc>An arrow pointing left</desc>
	<path etc.../>
</symbol>

Presentational use

Now, for useing those symbols as purely presentational icons:

  • Use role="presentation" on the SVG
<svg role="presentation">
    <use xlink:href="sprite.svg#left-arrow"/>
</svg>

That's it. But for SVG icons as 'content' we should do more:

Content use

  • Add role="img"
  • Add a title attribute
  • Add a title element with unique id
  • Add aria-labelledby="title-id"
<svg role="img" title="Back" aria-labelledby="title-back1">
	<title id="title-back1">Back</title>
    <use xlink:href="sprite.svg#left-arrow"/>
</svg>

This ensures the best accessibility AFAIK. Again: I am no a11y expert, so please correct me when I'm mistaken.

Thoughts?

Source information:

@fvsch
Copy link

fvsch commented Sep 29, 2015

So I rewrote my tests, added some more, and tested in VoiceOver+Safari (Mac 10.10) and JAWS16+IE11 and JAWS16+Firefox40 (Win7).
I also started to write up my conclusions:
https://dl.dropboxusercontent.com/u/145744/accessible-svg-icon/test.html

Tests in other screen readers are welcome. :)

@davidhund
Copy link
Author

Awesome @fvsch — I guess I should update this gist.

Also: would it be wise to advise SVG Sprite Icon tools to not add <title> and <desc> to <symbol>s? According to your tests they're of little use and it would reduce the file-size of the sprite.svg quite a bit…

@fvsch
Copy link

fvsch commented Sep 30, 2015

would it be wise to advise SVG Sprite Icon tools to not add <title> and <desc> to <symbol>s?

It would reduce the filesize a bit, yes. On the accessibility side: they don't hurt, but their usefulness is limited because support is partial and there are authoring difficulties (irrelevant titles, titles that come from the original filename, titles are probably not localized).

Many posts on SVG sprites say "use <title> for accessibility", which turns out to be harmful because that's a very partial solution, and authors will not explore other, better solutions. We probably need to change that.

So I'm torn on <title> in <symbol>s:

  • Pro: still read by some screen readers
  • Con: authors might think it covers their accessibility needs
  • Con: small file size increase

Personally I don't use or output <title>s in my SVG sprites, and rely on accessible information in the HTML document itself. If more screen reader testing shows it works, I would recommend that.

@fvsch
Copy link

fvsch commented Oct 1, 2015

Quick results with NVDA and Firefox:

  • SVG in flow content, no role attribute: not read
  • SVG in flow content, role="img": the aria-label attribute is read twice.
  • SVG in button: aria-label is read (once, or twice if using role="img".
  • SVG in link: similar as with button, plus some instances when the title attribute is read too.
  • aria-label on parent element works well, including for a <span> (for the record, in my tests JAWS and VoiceOver wouldn't read anything for <span aria-label="…">), and since we hide the icon itself with aria-hidden="true" we avoid labels that are read twice.

For inline icons:

  • NVDA reads the <text> element but not the paths
  • aria-hidden works well for hiding the icon
  • aria-label works for reading an accessible label, and it prevents NVDA from reading the text element in the icon (which seems to be what the spec asks for)

So the situation with NVDA is pretty good. It will not read <title> elements in an external sprite, but providing accessible text with aria-label works well. If you use both aria-label and role="img" it gets a bit verbose, because the label is said twice.

@fvsch
Copy link

fvsch commented Oct 1, 2015

Based on NVDA, JAWS and VoiceOver results, some highlights:

  1. Providing alt text for icons outside of links and buttons can be tricky. <svg role="img" aria-label="…"> works in VoiceOver and NVDA, and looks like a logical option based on my understanding of the ARIA spec, so I would recommend this markup.
  2. For links and buttons that only contain an icon, the widely supported markup is <button aria-label="…"><svg aria-hidden="true"></svg></button> (or similarly for a link). The runner-up is adding the text alternative on the icon itself with <svg role="img" aria-label="…"> (fails in JAWS+IE11 inside buttons).
  3. If your icon is next to a visible text label, you could use aria-hidden on the icon and let the screen reader read the visible label. If you're hiding that label on smaller screens, consider using a style that hides it visually but not from screen readers, such as:
.visuallyHidden {
  position: absolute;
  /* avoid 0px (ignored in some screen readers since the element is not rendered) */
  width: 1px;
  height: 1px;
  overflow: hidden;
}

Options that are not really useful:

  • title attribute, not reliable enough.
  • <title> element inside the <symbol>s in the SVG sprite. Read by VoiceOver only. Also hard to localize and to fill with text that is relevant in every context the icon is used.

@davidhund
Copy link
Author

@fvsch good stuff!

  • The 'double label' issue in NVDA/Fx was mentioned by @bramd and seems a bug
  • For 1): this works in Jaws too, right?
  • For 2): why the 'runner up'? Where does aria-label on a link/button fail?

Based on the above would it be safe to say that if you can add aria-label to a parent element (span, link, button) is the most robust way? This does not get read 2x by NVDA (while it is when the label is on the svg)?

@davidhund
Copy link
Author

So @fvsch the following would be a good summary, so far?
(My main doubt is re: span[aria-label] in the 2nd example…)

Standalone Icons as decoration

<svg aria-hidden="true">
  <use xlink:href="#symbol-id"></use>
</svg>

✔ VoiceOver | ✔ NVDA | ✔ JAWS 16

Standalone Icons as content

<svg role="img" aria-label="Text alternative">
  <use xlink:href="#symbol-id"></use>
</svg>

✔ VoiceOver | ✔ NVDA (label read twice) | ✔ JAWS 16

Note: the aria-label is read twice in NVDA/Fx.
The label on a parent element, however, has issues elsewhere (?)

<span aria-label="Text alternative">
  <svg aria-hidden="true">
    <use xlink:href="#symbol-id"></use>
  </svg>
</span>

✖ VoiceOver | ✔ NVDA | ✖ JAWS 16

Standalone Icons as interactive content

(Examples work with A or BUTTON)

<a href="#" aria-label="Text alternative">
  <svg aria-hidden="true">
    <use xlink:href="#symbol-id"></use>
  </svg>
</a>

✔ VoiceOver | ✔ NVDA | ✔ JAWS 16 / Fx40 + IE11

The following makes more sense but has issues with JAWS:

<a href="#">
  <svg role="img" aria-label="Text alternative">
    <use xlink:href="#symbol-id"></use>
  </svg>
</a>

✔ VoiceOver | ✔ NVDA | ✔ JAWS 16 / Fx40 | ( ✖ JAWS 16 / IE11 in button)

@fvsch
Copy link

fvsch commented Oct 2, 2015

Your summary is almost right, but the "Standalone Icons as content" is actually more tricky.

In JAWS (JAWS16+IE11), <svg role="img" aria-label="Text alternative"> outside of an interactive element is not read. aria-label on a span is not read either. The only thing in my test page that works for vocalizing an illustration icon was this:

<span title="">
  <svg aria-hidden="true"></svg>
</span>

So if we want to cover JAWS, we might try this:

<span title="The label">
  <svg role="img" aria-label="The label"></svg>
</span>

We'd have to test this solution specifically, because right now in the test page I'm not testing this belts-and-braces markup. Does it work reliably in JAWS and other screen readers? Does it work both with <svg><use/></svg> and with <svg><!-- many paths here --></svg>?

@fvsch
Copy link

fvsch commented Oct 2, 2015

If we didn't have to support JAWS we could just recommend this:

Hide a decorative icon from screen readers

<svg aria-hidden="true"></svg>

✔ VoiceOver+Safari | ✔ NVDA+Fx | ✔ JAWS16+IE11

Provide accessible text for an icon

<svg role="img" aria-label="Text alternative"></svg>

✔ VoiceOver+Safari | ✔ NVDA+Fx (label read twice) | ✖ JAWS16+IE11 (works in links only)

@fvsch
Copy link

fvsch commented Oct 2, 2015

UPDATE!!!1

Turns out I had ommitted to test <title> elements inside the HTML page's <svg> icons! Aaaand they work wonders. Updated test page: https://dl.dropboxusercontent.com/u/145744/accessible-svg-icon/test.html

Results:

  • Perfect in JAWS16+IE11
  • Alright in NVDA (still saying the text twice)
  • Works in VoiceOver (requires role="img" for images outside of interactive elements)

Hide a decorative icon from screen readers

<svg aria-hidden="true">
  <use xlink:href="sprite.svg#my-icon"></use>
</svg>

✔ VoiceOver+Safari | ✔ NVDA+Fx | ✔ JAWS16+IE11

Provide accessible text for an icon

<svg role="img">
  <title>Text alternative</title>
  <use xlink:href="sprite.svg#my-icon"></use>
</svg>

✔ VoiceOver+Safari | ✔ NVDA+Fx (text is read twice) | ✔ JAWS16+IE11

Note: the role="img" is only required by VoiceOver for reading icons outside of buttons or links. We recommend using it, to be on the safe side.

@davidhund
Copy link
Author

Thanks for your work @fvsch, really great to have test-results.
So we don't need aria-labelledby for the title elements? That's great (less authoring).

Your last snippet (title el in svg), does that work for icons in interactive els (A, BUTTON)? Or do those still need an aria-label?

@fvsch
Copy link

fvsch commented Oct 3, 2015

does that work for icons in interactive els

Yep, it works in interactive elements and in general flow, no need for aria-label (I do like the aria-label approach a tiny bit better, but support is more limited).

For the record I reached out to @chriscoyier to offer writing a guest post on CSS-Tricks. Since they have published a handful of pieces on SVG sprites (I'm linking to them in our internal documentation at work, for instance), that seems like a good place to publish a "definitive (as of 2015)" mini-guide.

@davidhund
Copy link
Author

Hey @fvsch — that's a good idea and exactly the reason I started looking into this topic in the first place ;-)

It would be great to use/advocate the most accessible markup for SVG Sprites and a high profile platform such as CSS Tricks would help a lot.

@anil1687
Copy link

Hi @fvsch,

I'm facing same issue regarding NVDA+FF reading text twice. Do we have any alternative for this.

@francishogue
Copy link

francishogue commented Oct 5, 2016

Can anyone make that test link from dropbox accessible again? (https://dl.dropboxusercontent.com/u/145744/accessible-svg-icon/test.html). Thank you!

@alexvb
Copy link

alexvb commented Oct 30, 2016

@fvsch It seems that the test page has gone. Could you make available again?

@davidhund
Copy link
Author

Unfortunately the tests have gone from @fvsch his Dropbox pages, for implementation I generally refer to his excellent guide at: https://fvsch.com/code/svg-icons/how-to/#section-adding

@md-azam12
Copy link

md-azam12 commented Jun 12, 2017

try this-->

<svg role="img" aria-labelledby="uniqueId_title">
      ....
    <title id="uniqueId_title">ThumpUp song</title>
</svg>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment