Can Auto-Playing Videos be Accessible?

Andrew Spencer

Auto-playing videos are used on many websites to add visual interest to the page. These videos are typically short, muted videos that supplement the text content on the page. They can be engaging, entertaining, or informative, but, they can also be overwhelming or even dangerous for users who are prone to motion sickness or seizures.

As designers and developers, we should be mindful that our users will have an array of preferences when it comes to auto-playing videos and we should strive to support all potential user preferences and input types. In short, we should ensure all users can control how much motion and animation they see. However, this can be quite complicated to achieve in practice.

Let’s create an auto-playing video component that does its best to consider all the various accessibility needs of users. For our component, we’ll add a muted, auto-playing video. It should be noted that we should never auto-play a video with sound as it’s annoying for all users and, as outlined in WCAG CS 1.4.2, extra frustrating for people using screen readers that rely on audio cues to navigate content.

Accessibility means considering countless permutations of user preferences, abilities, locations, input types, bandwidth speeds, and more. While it may be difficult to appropriately account for every single unique user type, we should do our very best. Some areas of video accessibility that we won’t touch on in depth are bandwidth and device capability. We should be mindful that not everyone accessing this content will be doing so on the latest devices with perfect internet speed. We should be sure to use smaller video files, compress our files, and keep videos short to reduce the amount of data that needs to be downloaded. Vittorio Giovara has a great article on the topic of lowering video quality to improve accessibility.

Step 1: identify the types of users we will need to account for

It’s helpful to identify the different preferences users might have and the input methods they could be using before we write any code. This way we can appropriately test the final feature to make sure we are solving for each type of user.

User preferences

  • Users who are ok with all motion
    • This means the video can auto-play for these users
  • Users who are ok with some motion
    • This means we should add controls to pause and play the video
  • Users who don’t want any motion
    • This means don’t auto-play the video for these users
  • Users who have low vision
    • This means video controls must have a color contrast ratio that at least passes WCAG AA
  • Users who have no vision
    • This means the video content needs to be described in the text so it can be read by a screen reader

Types of user input

The controls for the video and the elements themselves must be navigable with all user input methods, including…

  • Mouse
  • Keyboard
  • Touch
  • Screen reader

Step 2: Get building

Now that we have done our best to identify all of the different user preferences we will need to account for, let’s write some code! First, we can add a video tag and include specific attributes that will ensure it auto-plays and loops on all devices.

<video class="video" autoplay playsinline loop muted disablepictureinpicture background>
  <source preload="none" src="_video file_" type="video/mp4" />
  Your browser does not support the video tag.
</video>

Step 3: Address users who don’t want any motion

Modern browsers now support a media query called prefers reduced motion that taps into device-specific settings to determine whether the user wants to see motion or not. Michelle Barker has a great article on this topic in Smashing Magazine. For our component, we can use this media query to show a paused video to users who prefer reduced motion. We’ll also want to make sure there are obvious video controls to allow the user to play the video if they want to—it’s their choice!

This can be done in a few ways. In our case, we’ll use CSS to toggle between two instances of the video tag depending on whether the user has turned on prefers-reduced-motion for their device. The logic goes as follows:

  • If prefers-reduced-motion = no-preference : Show the auto-playing video we setup in step 2
  • If prefers-reduced-motion = reduce : Show a paused video with video controls

To build this, add a second video tag with attributes set to load a video that is paused by default and shows controls so it can be played by the user if they choose to do so. Add a class so we can identify this video tag with CSS.

<video class="video" autoplay playsinline loop muted disablepictureinpicture background>
  <source preload="none" src="_video file_" type="video/mp4" />
  Your browser does not support the video tag.
</video>
<video class="video reduced-motion" controls playsinline loop muted disablepictureinpicture background>
  <source preload="none" src="_video file_" type="video/mp4" />
  Your browser does not support the video tag.
</video>

Now we can add a splash of CSS to put the prefers-reduced-motion media query to work and toggle between showing the looping video and the reduced motion version.

.video {
  width: 100%;

  @media (prefers-reduced-motion: reduce) {
    display: none;
  }
}
.video.reduced-motion {
  display: none;

  @media (prefers-reduced-motion: reduce) {
    display: block;
  }
}

Step 4: Address users who are ok with some motion

Not all users who are distracted by motion have changed their device settings to prefer reduced motion. We should provide a way for these users to still pause the video. Again, the focus here is to give control back to users so they can decide how much motion they see.

To do this, we will need to add a control to the original video to allow users to pause it. We could use the browser default option here. But I think we can do better and make something subtle enough to not overly distract from the video itself, yet still prominent enough that users can find it when they want to pause a video.

Here is a mockup of one design option—a pause button on a white background in the bottom right corner of the video frame.

`Screenshot of a pause button in the bottom right corner of a square video frame.`

To build this, first, add a toggle button to the HTML with a play and pause icon inside. Then, hold the button and video tags together by wrapping them with a <div>.

<div class="video-container">
 <button class="video-controls">
  <svg class="pause-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="<http://www.w3.org/2000/svg>">
    <path d="M6.66667 2.66675H4V13.3334H6.66667V2.66675Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
    <path d="M12 2.66675H9.33331V13.3334H12V2.66675Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
  </svg>
  <svg class="play-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="<http://www.w3.org/2000/svg>">
    <path d="M3.33333 2L12.6667 8L3.33333 14V2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
  </svg>
 </button>
 <video class="video" autoplay playsinline loop muted disablepictureinpicture background>
   <source preload="none" src="_video file_" type="video/mp4" />
  Your browser does not support the video tag.
 </video>
 <video class="video reduced-motion" controls playsinline loop muted disablepictureinpicture background>
   <source preload="none" src="_video file_" type="video/mp4" />
   Your browser does not support the video tag.
 </video>
</div>

Next, add some CSS to style the button appropriately.

.video-container {
  position: relative;
  overflow: hidden;
}

.video-controls {
  position: absolute;
  z-index: 999;
  bottom: 0px;
  right: 0px;
  padding: 0.75rem;
  background-color: white;
  color: black;
  border: none;

  @media (prefers-reduced-motion: reduce) {
    // the browser default video controls will display if the user prefers reduced motion
    display: none;
  }
}

Use some more CSS to show one icon at a time, depending on if the video is playing or paused.

.pause-icon {
  display: block;
}
.play-icon {
  display: none;
}

.paused {
  // If the video is paused, show the pause icon
  & .pause-icon {
    display: none;
  }
  & .play-icon {
    display: block;
  }
}

To go one step further, we can use CSS to hide or show the control depending on whether the user is hovering or focusing on the video itself.

.video-container {
  @media (hover: hover) {
    &:hover .video-controls,
    &:focus-within .video-controls,
    .video-controls:hover,
    .video-controls:focus {
      bottom: 0;
      right: 0;
    }
  }
}

.video-controls {
  transition: all 350ms ease-out;

  // Hide the controls outside of the frame IF the user can hover
  @media (hover: hover) {
    bottom: -100px;
    right: -100px;
  }
}

Next, we’ll need some JavaScript to get things up and running!

// Support keyboard input for controlling the looping video
function toggleVideoKeyboard(e) {
  if(e.key == " " || e.key == "Enter") {
    e.preventDefault(e.target);
    toggleVideo(e.target);
  }
}

// Event handler for playing and pausing the looping video
function toggleVideo(e) {
  if (e.nextElementSibling.paused) {
    e.classList.remove('paused');
    e.nextElementSibling.play();
    e.setAttribute('aria-pressed', 'false');
  } else {
    e.classList.add('paused');
    e.nextElementSibling.pause();
    e.setAttribute('aria-pressed', 'true');
  }
}

Capture click and keydown events to the button that controls playing and pausing the video.

<button class="video-controls" onclick="toggleVideo(this)" onkeydown="toggleVideoKeyboard(event)">

Step 5: Address users who have low vision

Now we’ll need to ensure the video is accessible via a screen reader. Let’s add some aria attributes to our HTML to make this a better experience for users interacting with our component using a screen reader.

<div class="video-container">
 <button class="video-controls" onclick="toggleVideo(this)" onkeydown="toggleVideoKeyboard(event)" role="button" aria-pressed="false" aria-label="Pause video">
  <svg class="pause-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="<http://www.w3.org/2000/svg>">
    <path d="M6.66667 2.66675H4V13.3334H6.66667V2.66675Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
    <path d="M12 2.66675H9.33331V13.3334H12V2.66675Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
  </svg>
  <svg class="play-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="<http://www.w3.org/2000/svg>">
    <path d="M3.33333 2L12.6667 8L3.33333 14V2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
  </svg>
 </button>
 <video class="video" aria-label="_insert description of the video_" autoplay playsinline loop muted disablepictureinpicture background>
   <source preload="none" src="_video file_" type="video/mp4" />
  Your browser does not support the video tag.
 </video>
 <video class="video reduced-motion" aria-label="_insert description of the video_" controls playsinline loop muted disablepictureinpicture background>
   <source preload="none" src="_video file_" type="video/mp4" />
   Your browser does not support the video tag.
 </video>
</div>

Code example of how to make auto-playing videos accessible

Putting it all together now, here is the HTML, CSS, and Javascript code we added for our auto-playing video component.

HTML

Ensures the video will auto-play when it should, adds accessibility attributes, and includes a custom play/pause control.

<div class="video-container">
 <button class="video-controls" onclick="toggleVideo(this)" onkeydown="toggleVideoKeyboard(event)" role="button" aria-pressed="false" aria-label="Pause video">
  <svg class="pause-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="<http://www.w3.org/2000/svg>">
    <path d="M6.66667 2.66675H4V13.3334H6.66667V2.66675Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
    <path d="M12 2.66675H9.33331V13.3334H12V2.66675Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
  </svg>
  <svg class="play-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="<http://www.w3.org/2000/svg>">
    <path d="M3.33333 2L12.6667 8L3.33333 14V2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
  </svg>
 </button>
 <video class="video" aria-label="_insert description of the video_" autoplay playsinline loop muted disablepictureinpicture background>
   <source preload="none" src="_video file_" type="video/mp4" />
  Your browser does not support the video tag.
 </video>
 <video class="video reduced-motion" aria-label="_insert description of the video_" controls playsinline loop muted disablepictureinpicture background>
   <source preload="none" src="_video file_" type="video/mp4" />
   Your browser does not support the video tag.
 </video>
</div>

CSS

Styles the video and the custom play/pause control.

.video {
  width: 100%;
  @media (prefers-reduced-motion: reduce) {
    display: none;
  }
}

.video.reduced-motion {
  display: none;

  @media (prefers-reduced-motion: reduce) {
    display: block;
  }
}

.video-controls {
  position: absolute;
  z-index: 999;
  bottom: 0px;
  right: 0px;
  padding: 0.75rem;
  background-color: white;
  color: black;
  border: none;
  transition: all 350ms ease-out;

  @media (prefers-reduced-motion: reduce) {
    // the browser default video controls will display if the user prefers reduced motion
    display: none;
  }
}

.pause-icon {
  display: block;
}

.play-icon {
  display: none;
}

.paused {
  // If the video is paused, show the pause icon
  & .pause-icon {
    display: none;
  }
  & .play-icon {
    display: block;
  }
}

.video-container {
  position: relative;
  overflow: hidden;

  @media (hover: hover) {
    &:hover .video-controls,
    &:focus-within .video-controls,
    .video-controls:hover,
    .video-controls:focus {
      bottom: 0;
      right: 0;
    }
  }
}

.video-controls {
 // Hide the controls outside of the frame IF the user can hover
  @media (hover: hover) {
    bottom: -100px;
    right: -100px;
  }
}

Javascript

Make the play/pause control functional.

// Support keyboard input for controlling the looping video
function toggleVideoKeyboard(e) {
  if(e.key == " " || e.key == "Enter") {
    e.preventDefault(e.target);
    toggleVideo(e.target);
  }
}

// Event handler for playing and pausing the looping video
function toggleVideo(e) {
  if (e.nextElementSibling.paused) {
    e.classList.remove('paused');
    e.nextElementSibling.play();
    e.setAttribute('aria-pressed', 'false');
  } else {
    e.classList.add('paused');
    e.nextElementSibling.pause();
    e.setAttribute('aria-pressed', 'true');
  }
}

Yes, auto-playing videos can be accessible

That’s it! Now we have an auto-playing video component that is extra mindful of users’ preferences and strives to be as accessible as possible. There are many ways that a component with similar functionality could be created but hopefully this example can be useful for you. If you want to learn more about accessibility best practices, you can read more articles on the thoughtbot blog or, if you’re a design, checkout our color contrast plugin for Figma.

Here is a CodePen demo of the component we just put together.

See the Pen Looping video by Andrew Spencer (@iam_aspencer) on CodePen.