Skip to content

Instantly share code, notes, and snippets.

@davidhund
Last active August 30, 2024 17:03
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:

@davidhund
Copy link
Author

Whipped up a quick test-page with inline svg sprite at: http://codepen.io/davidhund/pen/WQoXwy

@davidhund
Copy link
Author

Apparently Firefox/NVDA reads out a double label for an 'image' https://twitter.com/bramduvigneau/status/648225142687010817

@davidhund
Copy link
Author

@davidhund
Copy link
Author

@davidhund
Copy link
Author

Note: Stevefaulkner mentions that NVDA reports multiple images in IE.

Could this be the cause of what @bramduvigneau noticed (“multiple image labels”)?

Steve notes that this can be avoided if you add role="presentation" to child elements (path, line, etc) of our SVG or Symbol.

Compare https://dl.dropboxusercontent.com/u/377471/SVG/adobesimplesvgtest1.html with https://dl.dropboxusercontent.com/u/377471/SVG/adobesimplesvgtest4.html

PS: I doubt this is a very practical thing to do, but maybe tools like svg-sprite can handle this too?

@fvsch
Copy link

fvsch commented Sep 28, 2015

For the record at work we've been using external symbol sprites, no <title> in symbols, and this for links or buttons:

<a href="...">
    <svg aria-hidden="true"><use /></svg>
    Link or button with text label in content
</a>

<button aria-label="Link or button without text label in content">
    <svg aria-hidden="true"><use /></svg>
</button>

Basically we figured that having a screen reader read the SVG icon, and having authors providing accessible text for the icon on the icon itself (often generated by a templating function), was unlikely, so we were better off trying to provide accessible text in context or using aria-label, and "hide" the icon altogether.

@davidhund
Copy link
Author

Thanks @fvsch — really helpful to hear how you approach this.

Interesting use of aria-hidden for all icons. What issues does it avoid over role="presentation"?

Your conclusion sounds reasonable, but I wonder how you deal with content icons that are not part of interactive elements (such as links or buttons). Do you add role="img" and title etc.? Or do you add a aria-label="foo" on parent elements (such as a span)?

@fvsch
Copy link

fvsch commented Sep 28, 2015

@davidhund, if I'm not mistaken:

  • aria-hidden="true" will prompt AT to ignore the SVG element and its descendants.
  • role="presentation" basically tells AT that the SVG element should be seen as a <span>. There are special cases where it's meant to neutralize the native semantics of an element and some of its descendants, for instance if you use it on a <ul> or <table> it might neutralize the semantics of the <li>s and <td>s below it, but I don't think we can rely on that for a SVG element.

Based on that, I figured that role="presentation" was not fail-safe if I wanted AT to basically ignore the SVG icon and use the contextual text (plain text or aria-label on parent) only.

how you deal with content icons that are not part of interactive elements

We rarely have that use case, because our icons are almost always in buttons and links, and when they're not it's often a coding mistake (there should be a A or BUTTON). Also if they are not in a button or link, they might be a decoration that goes with a label (for instance an icon that goes alongside an error or warning message).

When we do want to give alternative text for a SVG graphic outside of a link or button:

  • If it's a complex illustration that we don't need to manipulate with CSS and/or JS, we'll probably use <img src="illustration.svg" alt="Relevant alt text">.
  • If we need to manipulate it with CSS and/or JS, we'll probably inline the whole icon as a <svg> element with a <title> element, and we will probably avoid putting that content image in our SVG sprite(s).

@fvsch
Copy link

fvsch commented Sep 28, 2015

I've done some reading (of the ARIA spec re. role="img" and accessible text computation), and I looked more closely of the test results for VoiceOver, and barring further results I'd recommend this:

  • For presentational icons, that are purely decorative or enhance a visible text label: <svg aria-hidden="true"><use xlink:href="#symbol-id"></use></svg>
  • For "content" icons that need an accessible text alternative, <svg role="img" aria-label="Text alternative"><use xlink:href="#symbol-id"></use></svg> (aria-labelledby referencing an element in the document is also an option, but it's harder to manage so I would only use it as an exception for very specific content, long alternatives, etc. Not sure it would be useful for icons.)

I would not bother with adding <title> elements, let alone aria-labelledby attributes on the symbols themselves, but if people want a belt-and-braces approach, that's still an option. The accessible texts in the specific instances an icon is used should have priority over the default text alternative in the sprite (says the ARIA spec).

@davidhund
Copy link
Author

@fvsch Thanks for this. It is exactly the kind of feedback I was looking for.

I really like your solution: it's straight-forward, DRY and KISS 😄
Very promising: the one thing that remains, IMO, is a thorough a11y test: feedback from daily AT users (@bramd?) would be great…

My hope (and reason for my questions) is that we can come to a simple, accessible, markup pattern to use with SVG (Sprite) Icons. Hopefully people and blogs pick this up.

@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