Alt Text for Accessible Animations

Sean O'Shea Background

Sean O'Shea

Alt. An illustration of a colorful rocket blasting off into the night sky.

In order to make the web equally perceivable for all users, content creators and developers must provide text alternatives for any non-text content. The most ubiquitous example of this is alt text for images. But how do we provide alternative text for things like time-based animations using SVG or HTML Canvas?

Animating a Rocket Launch

We recently launched Brighter2021.com, a site where visitors can share their wish for the year and raise money for the Urban Ministries of Durham. After a wish is submitted, an animation plays that shows the wish flying into the open hatch of a waiting rocket ship that then blasts off into the starry sky. This animation is integral to the site experience, so it was important to ensure that all users would be able to experience it as fully as possible.

Setting a Role Attribute

The rocket animation is a collection of SVGs and <div> elements, animated mostly with javascript-triggered CSS transitions. Before we can provide accessible alt text, we have to make sure the animation itself has appropriate semantics for screen readers and other assistive technologies to interpret. To do this, we can specify an ARIA role attribute on an element wrapping all of the elements we'll be animating.

<div id="rocket-animation" role="img">
  <!-- all DOM elements that are part of the animation go here -->
</div>

The img role is most appropriate in this case (at the time of writing):

The ARIA img role can be used to identify multiple elements inside page content that should be considered as a single image. These elements could be images, code snippets, text, emojis, or other content that can be combined to deliver information in a visual manner.
MDN Web Docs, ARIA: img role

The key bit here is other content that can be combined to deliver information in a visual manner—which is exactly what our animation is.

The img role has the added benefit of "flattening" all of the child elements in the eyes of most assistive technologies since true images on the web aren't permitted to have child elements. This means we don't have to worry about specifying role attributes on each of the elements within our animation to override their native semantics.

Most of the time, a good way to add alternative text to an element with role="img" is to add an ARIA label attribute as well. However, this would have the undesired effect of "displaying" the animation's text alternative to screen readers along with the rest of the page content when it loads! When it comes to accessibility, we want to provide an equivalent experience to all users. That means ensuring the animation alternative is hidden until the user submits their wish—just like the animation. And we want to make sure that the animation experience happens automatically—just like the animation.

Using ARIA Live Regions

This is where ARIA live regions come into play. Live regions are used to tell assistive technologies which sections of a document they should watch for changes, and how and when to announce those changes when they occur.

Dynamically adding animation description text to a live region as the animation begins will allow assistive technologies to announce the description at the appropriate time. Removing an inline style attribute containing display: none; is an effective way to trigger live region without having to add the content itself via javascript.

<div aria-live="polite">
  <div id="animation-description-text" style="display: none;">
    <p><strong>Animation Description:</strong> The text of your wish slides offscreen and the other elements on the webpage fade out. A small rocket ship, crewed by Ava the owl, appears with an open hatch. Your wish flies into the open hatch into Ava's beak. The hatch closes. The rocket ship blasts off into the star-filled night sky.</p>
  </div>
</div>
function describeAnimation() {
  let animationDescriptionText = document.getElementById('animation-description-text')
  animationDescriptionText.removeAttribute('style')
}
function startAnimation() {
  describeAnimation()
}

In this case, we should use the polite value, which ensures the update doesn't interrupt users that are actively navigating within the page.

Tying the Description to the Animation

The last step is to semantically link the animation elements to the text of the description. For this, we can use the aria-describedby attribute, which allows authors to reference the ID of the element that describe the object the attribute is on.

Putting it all together looks like this:

<!-- animation -->
<div id="rocket-animation" role="img" aria-describedby="animation-description">
  <!-- all DOM elements that are part of the animation go here -->
</div>

<!-- animation description -->
<div id="animation-description" aria-live="polite">
  <div id="animation-description-text" style="display: none;">
    <p><strong>Animation Description:</strong> The text of your wish slides offscreen and the other elements on the webpage fade out. A small rocket ship, crewed by Ava the owl, appears with an open hatch. Your wish flies into the open hatch into Ava's beak. The hatch closes. The rocket ship blasts off into the star-filled night sky.</p>
  </div>
</div>
function describeAnimation() {
  let animationDescriptionText = document.getElementById('animation-description-text')
  animationDescriptionText.removeAttribute('style')
}
function startAnimation() {
  describeAnimation()
}

It may look a bit like "div soup", but it's an important and necessary step to make sure content-critical animations are perceivable by everyone who visits your site.

Hear it in Action

To hear it in action, enable VoiceOver or another screen reader or emulator and head over to the Brighter 2021 site. Launch your own wish into the stars, and wish for a better, accessible web for all.