ally.js
ally.js copied to clipboard
Utility to improve SVG accessibility
Tips for Creating Accessible SVG by @LjWatson describes a few things we can do to <svg> content to improve its exposure in the accessibility tree. Some of those tips should probably be handled by an automated build-step, rather than the SVG author (as these "hacks" tend to change over time). Here are Leonie's tips expressed in JavaScript (untested, quickly written while offline on a train):
Reinforce <svg role="img">
var svg = document.querySelector('svg');
if (!svg.getAttribute('role')) {
svg.setAttribute('role', 'img');
}
Reinforce <title> and <desc>
var svg = document.querySelector('svg');
var titles = svg.querySelectorAll('title');
[].forEach.call(titles, function(title) {
var container = title.parentNode;
addReference(container, 'aria-labelledby', title);
});
var descriptions = svg.querySelectorAll('desc');
[].forEach.call(descriptions, function(description) {
var container = title.parentNode;
// we *should* reference a description, but AT support makes us abuse label instead
// addReference(container, 'aria-describedby', description);
addReference(container, 'aria-labelledby', description);
});
Reinforce links
var svg = document.querySelector('svg');
var isValidTabindex = require('ally/is/valid-tabindex');
var links = svg.querySelectorAll('a');
[].forEach.call(links, function(link) {
if (!element.matches('svg a[*|href]')) {
return;
}
if (!link.getAttribute('role')) {
link.setAttribute('role', 'link');
}
if (!isValidTabindex(link)) {
link.setAttribute('tabindex', '0');
}
});
Utilities
above code uses the following utilities:
var uniqueIdCounter = 0;
var uniqueSalt = window.performance && window.performance.now() || Math.random();
var uniqueIdPrefix = 'generated-' + uniqueSalt + '-';
function ensureIdAttribute(element) {
if (element.id) {
return;
}
uniqueIdCounter++;
element.id = uniqueIdPrefix + uniqueIdCounter;
}
function addReference(element, attributeName, reference) {
ensureIdAttribute(reference);
var references = (element.getAttribute(attributeName) || '').split(' ').filter(Boolean);
if (references.indexOf(reference.id) !== -1) {
return;
}
references.push(reference.id);
element.setAttribute(attributeName, references.join(' '));
}
The addReference() function should probably be replaced by a proper implementation of DOMTokenList (jwilsson/domtokenlist, or necolas/dom-shims). ensureIdAttribute() would probably be made available at ally.element.id
Optimal point of integration
Checking for <title> and <desc> being present should be left to the validators. But above code is generic enough to be included in optimizer tools like SVGO. If SVGO were to perform these augmentations, we'd see a more immediate and wide-spread result (at least for websites and apps that have already cared about performance).
We should talk to @GreLI about how to get this done. Does it make sense for SVGO to offload this accessibility optimization to ally.js, or can't that be done (easily) because it's lacking a DOM interface?
Having thought about this topic a bit more, I've come to the conclusion that we should hide these "automatic aria enhancements" in a simple service called maintain/reinforced-aria (or maintain/reinforced or maintain/aria?). Enhancements should live in their own modules so they can be used by other components at will:
maintain/reinforced-ariarunsreinforce/*on init and fires up a MutationObserverreinforce/svgprovides a css selector to identify the elements the module can handle, as well as a callback taking such an element as the only argument, for which it will runreinforce/svg/*reinforce/svg/rolereinforce/svg/title-referencereinforce/svg/desc-referencereinforce/svg/link-role
I'm sure the SVG tips that spawned this idea are only the tip of the iceberg of things we can improve behind the scenes.
I wonder if the headers attribute can be filled with a simple heuristic to help exposure in AT (see accessible data tables, bin to test), unfortunately Safari and Accessibility Inspector on OSX don't show a label for any cells.
Most of these optimizations (so far) are one time attribute mutations that could easily be done in the build-pipeline. In case enough optimizations come together we should probably talk about providing a CLI that can read mutate and save static html files.
:+1: This is feeling a bit like a healthier take on ngAria....I love your thinking here!