# Background videos that loop with no pause button

Source: https://easeweb.dev/learn/background-video
Topics: Time limits and motion > Background videos can be paused (WCAG 2.2.2)
The fix: Either put a Pause button on the hero, visible without a hover, that pauses the video itself and keeps it paused, or cut the clip to five seconds or less and play it once, so it ends on a still frame. A pause that shows only on hover, or only under the reduced-motion setting, is not enough.
Test with: keyboard, touch, mouse, reduced motion, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html https://www.w3.org/WAI/WCAG22/Techniques/general/G4 https://www.w3.org/WAI/WCAG22/Techniques/general/G186 https://www.w3.org/WAI/WCAG22/Techniques/general/G152 https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/video https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A coffee roaster's home page opens on footage: roasted beans tumbling onto a pile, filling the hero behind the headline "Fresh coffee, roasted every Monday" and a Shop coffee button. The design is drawn clean, with nothing over the footage but the words, and the video is muted so that nobody is startled by sound. It loops: its eight seconds start again the moment they end, for as long as the page is open.

There is no pause button. Clicking the hero does nothing, and neither does holding the pointer over it. Pressing Tab moves from the menu to Shop coffee and on to the week's coffees below, past nothing that stops the video. On a phone the hero fills the first screen, and the beans keep tumbling behind every word there is to read.

## Why it fails

Success criterion 2.2.2 Pause, Stop, Hide applies to moving content that starts by itself, lasts more than five seconds, and is shown alongside other content. Footage behind a headline meets all three: `autoplay` starts it, `loop` keeps it going, and the headline, the button and the rest of the page sit beside it. So the page needs a way to pause, stop or hide it, and it has none: the video has no `controls`, and no button of the page's own calls `pause()`.

`muted` doesn't help here. It takes away the sound, which is what 1.4.2 Audio Control is about, but not the movement. Browsers let a muted video play by itself before the visitor has clicked anything, because it makes no sound, so a muted loop starts on almost every visit, where a video with sound is held back.

## Who is affected

The hero is the first thing on the page, so the movement starts before anyone has read a word, and stays in view while they read the headline and decide where to go next.

## What automation and AI miss

The audit flags every video with no `controls` that plays by itself without sound, muted or with no sound track, and loops or runs for more than five seconds. It says whether it found a button near it whose name says pause, stop or play, and whether that button is hidden or fully transparent at rest, or left out of the Tab order. It can't press the button to see whether the video really stops, or find a pause control further from the video or under another name, so each one is a lead for a person to check. A video that a script plays without the `autoplay` attribute is caught only if it is playing when the page has loaded. AI fixes often wrap the video in a check for the reduced-motion setting and stop there, which helps only people who have found that setting, or add a pause button that fades in when the pointer is over the hero, which the keyboard never reveals and a phone has no pointer to hover with.

## Before: a muted video that loops behind the headline

```html
<section class="hero">
  <video class="hero-video" src="beans.mp4" poster="beans.jpg" autoplay muted loop playsinline></video>
  <div class="hero-text">
    <h1>Fresh coffee, roasted every Monday</h1>
    <p>Single-origin beans, sent out the week they are roasted.</p>
    <a href="/shop">Shop coffee</a>
  </div>
</section>
```

```css
.hero { position: relative; display: grid; align-items: center; min-height: 70vh; overflow: hidden; }
.hero-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero-text { position: relative; padding: 2rem; color: #fff; }
```

- The video starts as the page loads, and `loop` plays its eight seconds again and again for as long as the page is open.
- `muted` silences it, but it keeps moving.
- It has no `controls`, and the page has no button of its own, so nothing pauses it, by mouse, keyboard or touch.

## After: a Pause button on the hero

```html
<section class="hero">
  <video class="hero-video" src="beans.mp4" poster="beans.jpg" muted loop playsinline aria-hidden="true"></video>
  <button type="button" class="hero-pause">
    <svg aria-hidden="true" focusable="false">
      <use href="#icon-pause"></use>
    </svg>
    <span class="visually-hidden">Pause background video</span>
  </button>
  <div class="hero-text">
    <h1>Fresh coffee, roasted every Monday</h1>
    <p>Single-origin beans, sent out the week they are roasted.</p>
    <a href="/shop">Shop coffee</a>
  </div>
</section>
```

```css
.hero { position: relative; display: grid; align-items: center; min-height: 70vh; overflow: hidden; }
.hero-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero-text { position: relative; padding: 2rem; color: #fff; }
/* A dark disc under a white icon, so it keeps its contrast over every frame. */
.hero-pause {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid #fff;
  border-radius: 50%;
  background: #21160f;
  color: #fff;
}
.hero-pause:focus-visible { outline: 3px solid #fff; outline-offset: 2px; box-shadow: 0 0 0 7px #21160f; }
```

```js
const video = document.querySelector('.hero-video');
const button = document.querySelector('.hero-pause');
// The icon and the hidden name follow the video's real state.
function show() {
  button.querySelector('use').setAttribute('href', video.paused ? '#icon-play' : '#icon-pause');
  button.querySelector('.visually-hidden').textContent =
    video.paused ? 'Play background video' : 'Pause background video';
}
video.addEventListener('play', show);
video.addEventListener('pause', show);
button.addEventListener('click', () => (video.paused ? video.play() : video.pause()));
// Still from the start for people who asked for less motion.
if (!matchMedia('(prefers-reduced-motion: reduce)').matches) video.play();
show();
```

1. Put a native `<button>` in the hero, before its words, so the keyboard reaches it before Shop coffee, and draw it in the top corner, where it comes first in reading order too. It is there at rest, with no hover, on every screen size.
2. Give it a dark disc behind a white icon, so it keeps its contrast over the lightest and the darkest frames of the footage.
3. Make it call `pause()` and `play()` on the video itself. The video stays paused until someone presses the button again.
4. Set the icon and the hidden name from the video's own `play` and `pause` events, between Pause background video and Play background video, so the button tells the truth even when the browser refuses to start the video.
5. Hide the video from screen readers with `aria-hidden="true"`: it only sets the mood, and the button says what it controls.
6. Take `autoplay` off and start the video from the script, leaving it still for people whose system asks for less motion. That is an extra: the button is what passes 2.2.2, and without the script nothing moves at all.

This fixes the Before and passes 2.2.2, with the loop and the design as drawn.

## After: a clip that plays once and stops within five seconds

```html
<section class="hero">
  <video class="hero-video" src="beans-4s.mp4" poster="beans.jpg" autoplay muted playsinline aria-hidden="true"></video>
  <div class="hero-text">
    <h1>Fresh coffee, roasted every Monday</h1>
    <p>Single-origin beans, sent out the week they are roasted.</p>
    <a href="/shop">Shop coffee</a>
  </div>
</section>
```

```css
.hero { position: relative; display: grid; align-items: center; min-height: 70vh; overflow: hidden; }
.hero-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero-text { position: relative; padding: 2rem; color: #fff; }
```

1. Cut the clip to four seconds, and never more than five. Movement that ends within five seconds is outside 2.2.2, so it needs no button.
2. Take `loop` off. The clip plays once and stops on its last frame, which stays on screen as a still for the rest of the visit.
3. Choose that last frame as carefully as a photo: it is what everyone sees once the movement is over, and it sits behind the headline.
4. Leave out any script that plays it again, on a timer or when the hero scrolls back into view. The five seconds are all the movement there is, not one round of it.
5. Hide the video from screen readers with `aria-hidden="true"`: it only sets the mood, and there is nothing in it to operate.

This fixes the Before and passes 2.2.2 with no button and no script. The design changes: the footage becomes a short opening shot that settles into a still.

## Which option to use

Both pass. The Pause button keeps the endless footage the design was built around. It costs a button in a corner of the hero and a few lines of script, and the beans move for everyone until they find it. The clip that plays once costs nothing to build, and nobody has to find anything, but the designer gives up the loop: the footage has to say what it needs to in four seconds and end on a frame that works as a photo. Choose the short clip when an opening shot is enough, and the Pause button when the moving footage is part of the brand.

## Implementation decisions

Pausing while the pointer is over the hero, or while focus is inside it, can stay as a kindness, but it is never the mechanism: Understanding 2.2.2 rules out a pause that lasts only while focus stays on the moving content. The same goes for a Pause button that appears only on hover. A phone has no pointer to hover with, and if it is hidden with `opacity: 0`, the keyboard reaches a button nobody can see; if it is hidden with `visibility: hidden`, the keyboard never reaches it at all.

The button needs what any icon button needs: a name, an icon with 3:1 contrast against everything behind it, at least 24 by 24 pixels (the button above is 44), a focus ring that shows on every frame (the white ring above has a dark band each side), and an icon and name that change together. A button that changes its name when pressed is not announced again by every screen reader, so check it with the ones your users have. Some sites write the word next to the icon ("Pause"), which helps people who don't know the symbol.

Keep the visitor's choice. Store it for the visit, for example in `sessionStorage`, and read it before starting the video, so a reload or the next page with the same hero doesn't set it moving again. Scripts that pause the video when the hero scrolls out of view, to save battery, are fine, but they must not play it again when it comes back if the visitor paused it.

Native `controls` give a way to pause too, but they draw a player bar over the hero, with a timeline and other buttons the hero doesn't need, so most designs use the single button above.

The reduced-motion setting is worth respecting, as the Pause button fix does by leaving the video still, and the short clip can do the same by leaving out `autoplay` when the setting is on. It is not a fix on its own: not everyone who is distracted by movement knows that setting is there, and 2.2.2 asks for a mechanism on the page. What the setting asks of animation that starts from an interaction is covered in [animations that ignore the reduced-motion setting](/learn/reduced-motion).

The short clip counts its five seconds from the moment the video starts moving, not from when the file finished loading. If editors can swap the hero video in a content system, check the length there, so a longer clip can't be published without the button.

The same failure shows up wherever footage moves by itself beside content. Each of these takes the same two fixes:

- A looping video in a section further down the page, such as a product shown turning on a loop.
- An animated GIF, WebP or Lottie file that stands in for a video and loops without end.
- A video embedded from Vimeo with its background option, or from YouTube with its controls turned off, set to play by itself and loop.

Tickers, carousels and other moving content that is not footage have their own guide: [tickers, carousels and animations that can't be paused](/learn/moving-content). A pulsing button or a spinning icon usually takes a different fix, ending by itself: [pulsing buttons, glowing badges and endless animations](/learn/endless-animations).

## Verify the fix

1. Load the page and time the video from the moment it starts moving. If it stops by itself within five seconds, note how long it moved and go to step 4; if it is still moving, go on.
2. Put the mouse aside and press Tab from the top of the page. The Pause button is visible before focus reaches it, shows a focus ring over the footage, and Enter or Space stops the video. Move focus on and the pointer around: the video stays still.
3. On a phone, without touching the hero first, tap Pause on the first screen. Check it can be seen over the lightest and darkest frames.
4. Scroll the hero out of view and back, then open another page and return. A paused video stays paused, and a clip that has ended doesn't play again.
5. With a screen reader, Tab to the button. It is announced as "Pause background video, button", and after it is pressed its name is "Play background video". Where the clip plays once, check that nothing about the video is read at all.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the hero, its video or its player script changes.

## Limits

This is a starting pattern, not a guarantee for every application. It covers movement only. Words over footage need their contrast on every frame, which is covered in [low-contrast text on brand colours, photos and hover states](/learn/text-contrast). Footage that flashes more than three times in any one second is a separate check under 2.3.1, made with a flash analysis tool: see [flashing banners and videos that can trigger seizures](/learn/flashing-content). A video that plays with its sound on has its own guide, [autoplaying sound with no way to stop it](/learn/autoplay-audio). A video meant to be watched is covered in [videos without captions, or with unchecked automatic ones](/learn/video-captions) and [videos a keyboard can't play, or with no captions button](/learn/video-player-controls). Whether the page is calm enough once the video has stopped still needs a person to judge.

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
