ally.js icon indicating copy to clipboard operation
ally.js copied to clipboard

Utility to improve SVG accessibility

Open rodneyrehm opened this issue 9 years ago • 2 comments

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?

rodneyrehm avatar Nov 22 '15 16:11 rodneyrehm

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-aria runs reinforce/* on init and fires up a MutationObserver
  • reinforce/svg provides 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 run reinforce/svg/*
  • reinforce/svg/role
  • reinforce/svg/title-reference
  • reinforce/svg/desc-reference
  • reinforce/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.

rodneyrehm avatar Nov 22 '15 22:11 rodneyrehm

:+1: This is feeling a bit like a healthier take on ngAria....I love your thinking here!

marcysutton avatar Nov 24 '15 19:11 marcysutton