Accessible Animated GIF Alternatives
At 4/19/2024
In my previous article, I explored a few different alternatives to the animated GIF with regard to performance, compatibility and feature parity. I found many promising formats, but one big potential hiccup: The two strongest contenders required separate HTML elements, img
for AVIF or video
for WebM. This raises a new question: Which HTML element is more accessible for the display of GIF-like animated clips?
For the purposes of this article, I’m defining a “GIF-like animated clip” as a short, silent, non-vector video or animated image file that plays inline with content. They often loop and usually play automatically.
True accessibility depends on real people interacting with real experiences. But as an imperfect substitute, we can base our requirements on portions of the Web Content Accessibility Guidelines (WCAG). Specifically, the element we choose should…
- Allow the viewer to control its playback
- Provide a text alternative
- Prevent motion where preferred
Let’s step through each requirement to build our accessible GIF-like animation pattern.
1. Playback Control
For any moving, blinking or scrolling information that (1) starts automatically, (2) lasts more than five seconds, and (3) is presented in parallel with other content, there is a mechanism for the user to pause, stop, or hide it unless the movement, blinking, or scrolling is part of an activity where it is essential…
Understanding SC 2.2.2: Pause, Stop, Hide (Level A)
Visitors may control the playback of video
elements via a context menu or visible controls when the controls
attribute is present:
<video controls autoplay loop muted playsinline
src="clip.mp4"></video>
Code language: HTML, XML (xml)
The img
element does not support playback controls. There are JavaScript libraries that attempt to remedy this, but they work by replacing the img
with a canvas
, which disqualifies them for the purposes of this article. iOS 17 and macOS 14 “Sonoma” add some native playback settings, but browser support is limited to Safari, and the UX is a little clunky.
2. Text Alternative
Provide text alternatives for any non-text content so that it can be changed into other forms people need, such as large print, braille, speech, symbols or simpler language.
Understanding SC 1.1: Text Alternatives
We’ve already ruled out img
for its lack of playback controls, but video
lacks an alt
attribute, and its support for captions won’t apply to silent videos. Thankfully, we can use aria-label
instead (as long as the controls
attribute is also present):
<video controls autoplay loop muted playsinline
aria-label="text alternative goes here"
src="clip.mp4"></video>
Code language: HTML, XML (xml)
But some visitors may rely on translation services, which sometimes overlook aria-label
attributes. No problem, let’s use aria-labelledby
and a separate descriptive element:
<video controls autoplay loop muted playsinline
aria-labelledby="video-label"
src="clip.mp4"></video>
<div id="video-label" aria-hidden="true">
text alternative goes here
</div>
Code language: HTML, XML (xml)
Definitely not as simple as the img
element’s alt
attribute, but not too unreasonable.
3. Reducing Motion
Some users experience distraction or nausea from animated content. For example, if scrolling a page causes elements to move (other than the essential movement associated with scrolling) it can trigger vestibular disorders.
Technique C39: Using the CSS reduce-motion query to prevent motion
The simplest way to support reduced motion preferences is to include controls
but not autoplay
. The viewer may choose to initiate the motion whenever they like:
<video controls loop muted playsinline
aria-labelledby="video-label"
src="clip.mp4"></video>
<div id="video-label" aria-hidden="true">
text alternative goes here
</div>
Code language: HTML, XML (xml)
If losing automatic playback is too large a departure from typical GIF behavior, we can use JavaScript to restore it based on the visitor’s preference:
// Save a reduced motion media query
const reduceMotionQuery = window.matchMedia(
'(prefers-reduced-motion: reduce)'
);
// Loop through all video elements and toggle behavior
function gifLikeVideos(enable) {
const videos = document.querySelectorAll(
'video[loop][muted][playsinline]'
);
[...videos].forEach((element) => {
if (enable) {
element.play();
} else {
element.pause();
}
});
}
// Toggle all videos based on the media query state
function onMotionChange({matches} = {}) {
gifLikeVideos(!matches);
}
// Listen for media query changes
reduceMotionQuery.addEventListener('change', onMotionChange);
// Set initial state
onMotionChange(reduceMotionQuery);
Code language: JavaScript (javascript)
Putting It All Together
Here’s our final pattern in action (apologies again to Jason):
It fulfills all three of our initial criteria:
- Visitors can control its playback.
- There is a text alternative that isn’t obscured from translation services.
- It does not autoplay until the viewer’s reduced motion preference is confirmed, and responds dynamically to that preference.
While the video
element has room for improvement, its native playback controls make video formats the most viable animated GIF alternatives when it comes to accessibility.
Big thanks to my teammates Scott Vandehey and Gerardo Rodriguez for their technical review, and to Adrian Roselli for pointing me toward a helpful discussion in the A11y Slack.