Just inlining SVG seems to be the easiest and most flexible icon system. But that chunk of <svg>
might have a <title>
, and you might be appying IDs to both of those elements for various reasons.
One of those reasons might be because you just want an ID on the icon to uniquely identify it for JavaScript or styling purposes.
Another of those reasons is that for accessibility, it’s recommended you use aria-labelledby
to connect the id and title, like:
<!-- aria-labelledby pointing to ID's of title and desc because some browsers incorrectly don't use them unless we do -->
<svg role="img" viewBox="0 0 100 100" aria-labelledby="unique-title-id unique-desc-id">
<!-- title becomes the tooltip as well as what is read to assistive technology -->
<!-- must be the first child! -->
<title id="unique-title-id">Short Title (e.g. Add to Cart)</title>
<!-- longer description if needed -->
<desc id="unique-desc-id">A friendly looking cartoon cart icon with blinking eyes.</desc>
<!-- all the SVG drawing stuff -->
<path d="..." />
</svg>
But now you include that SVG somewhere twice. Say you’re in Rails…
<%= render "/icons/icon.svg.erb" %>
<p>yadda yadda yadda</p>
<%= render "/icons/icon.svg.erb" %>
Now you’ll have two elements on the page with the exact same ID, which is… bad?
It’s definitely bad if you’re relying on that ID for anything JavaScript related, because JavaScript will only find the first one and that might be confusing and weird.
I’m not entirely sure if it’s bad for accessibility. Perhaps someone else can weigh in there. Assuming the titles are the same, my guess is that it won’t matter much.
It’s bad for HTML semantics, I suppose, but I’m always kinda meh on that if there are no repercussions.
If you’re really interested in fixing this issue, my go-to would be to pass in the ID’s to be used manually.
Again if you were in Rails, you could pass locals:
<%=
render(
partial: "parts/modules/search",
locals: {
svg_id: "my-icon",
title_id: "my-icon-title",
desc_id: "my-icon-desc"
}
)
%>
And then design the icons to use those locals, like
<svg title="<%= svg_id %> aria-labelledby="<%= title_id %>" ... >
<title id="<%= title_id %>>
...
</svg>
You could port that concept to any language. A React app could have:
<SVGIcon svg_id="..." title_id="..." />
A PHP app could set variables before an include:
$svg_id = "...";
$title_id = "...";
include("/icons/icon.svg.php");
Now it’s just on you to manage IDs to make them unique like we’ve always done with IDs.
This little post was inspired by Austin Wolf, who had this problem and thought through some solutions. This also included auto-generating unique IDs:
<svg aria-labelledby="star-6c84fb90-12c4-11e1-840d-7b25c5ee775a">
<title id="star-6c84fb90-12c4-11e1-840d-7b25c5ee775a">star icon</title>
// ...
</svg>
That also seems like a good solution to me.
I’d be interested to hear more thoughts!
If it’s bad? It’s not only bad, it’s simply not valid HTML to have duplicate IDs — and sure won’t pass the w3c validator, so i think it’s a no-go to not fix it.
That’s what I mean by meh. Validation is a wonderful tool for finding mistakes, but I wouldn’t get hung up on it. Or worse, assume that if your code validates it’s perfect.
Well, turns out there kind of are repercussions for valid HTML, as it is a WCAG Level A requirement
https://www.w3.org/TR/UNDERSTANDING-WCAG20/ensure-compat-parses.html
But in this case, failure to abide by the level A requirement doesn’t have a meaningfully negative impact on accessibility if the titles are the same.
Still, it’s one of those things I’d solve just to get aXe off my case.
The first rule of ARIA is: “If you can use a native HTML element or attribute with the semantics and behaviour you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.”
Applies perfectly here I think. Just tried it with VoiceOver. Just remove the ids and the
aria-labelledby
attribute. The title will be read by VoiceOver, and the user can optionally decide whether or not to go into the image and read the description. Most important when using inline SVG icons is to actually include a title (and possibly a description).I think the use of
aria-labelledby
in this way comes from Léonie Watson:It’s hard to argue against an experienced user like Léony Watson, however, the article you mention is from 2014. My test was VoiceOver with Firefox, not typically an combination you’d expect to work perfectly. Just tested in Safari as well, also perfect results. So you’re using an old workaround (which I would be wary to implement in the first place if I wouldn’t be able to touch the source code in years, since AT-clients also improve year by year), that seems to have become obsolete, that renders you into trouble… I’d revisit whether you still need the workaround :)
Screen reader support for the SVG title element has improved in the three years since I wrote the article Chris mentions, but not to the extent that ARIA is no longer needed. A quick test indicates that it is still not supported by Jaws or NVDA in Firefox.
I’d caution against making any design decisions based only on the behaviour of VoiceOver. The best stats we have available suggest that VoiceOver is used by less than 8% of screen reader users, with Safari the browser of choice for about the same percentage. Jaws and NVDA collectively account for more than 50% of screen reader use, with Firefox the browser of choice for more than 30% of the people who use a screen reader.
I feel like maybe this doesn’t matter from an accessibility standpoint. Using the same svg in multiple places produces duplicate id’s, but at the same time, the values of those titles and descriptions haven’t changed and thus the
aria-labeledby
is still going to technically report the correct values, even if they are pointing to a different SVG in the markup.The only issue I would see is JavaScript selection is obviously is a problem and HTML validation.
I use twig for almost all sites I create and this is exactly how I handle this issue, by passing in an ID. That said, if I was ever forced to use the same ID on multiple elements (I’ve come across this with plugins like “open table” reserve buttons that are rendered by a third party script.), I just swap my js selector to be more agnostic:
document.querySelectorAll('[id=some-id]')
Any reason to not just use
document.querySelectorAll('#some-id')
?Seems to work perfectly fine
A general thought, why not remove ID’s for commonly used SVG, and only use IDs where specifically required? Like we already would do with HTML?
Then, embed away without concern, especially if it’s just an icon, do you really need an ID in there?
In this context, where re-use is important, another approach would be to use aria-label instead of aria-labelledby. For example:
Personally I wouldn’t use ids on svgs at all.
If you need an icon to be clickable then wrap it in a span with an id.
That way you can have all the duplicates in the world and it wouldn’t make a difference.
Why don’t you simply use if you include the icon a second time? That should save bytes and resolve the problem with multiple IDs.
Here’s a thought… if we’re talking about icons, then they’ll probably just be decorative. So how about just hiding them from screen readers with
aria-hidden="true"
?I have a small (418-byte) SVG icon easily embedded into my CSS—and my CSS is so small (472 bytes without the SVG) I could easily pop it all into the HEAD. Yes, friends, a simple-minded text-only blog, based on Jeff Starr’s H5, FWIW. But our ol’ pal IE surely doesn’t like embedded SVG, so rather than struggle with IE hacks I’ve found it far less grief, in the long run, simply to use a url() reference to the little SVG file in my CSS. Happy wife, happy life, as it were.