# Autoplaying sound with no way to stop it

Source: https://easeweb.dev/learn/autoplay-audio
Topics: Images and media > Audio that plays by itself can be stopped (WCAG 1.4.2)
The fix: Either let the video wait until someone presses Play, or let it start muted, with buttons to pause it and turn the sound on.
Test with: keyboard, screen reader, autoplay settings
References: https://www.w3.org/WAI/WCAG22/Understanding/audio-control.html https://www.w3.org/WAI/WCAG22/Techniques/general/G171 https://www.w3.org/WAI/WCAG22/Techniques/general/G170 https://www.w3.org/WAI/WCAG22/Techniques/failures/F93 https://developer.chrome.com/blog/autoplay https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Autoplay

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 a short banner film of roasted beans tumbling onto a pile. The film starts by itself, loops, and plays its music out loud. The video has no controls, and nothing else on the page pauses or mutes it. Other sites play their music without a film: a background track that a script starts on the visitor's first click anywhere on the page, whether that click opened the menu or chose a product.

## Why it fails

Success criterion 1.4.2 allows sound to start by itself only if it stops within three seconds, or if the page gives a way to pause or stop it, or to turn its volume down apart from the system volume. Here the video has the `autoplay` attribute, no `muted` attribute, no `controls` and no button of its own, so the soundtrack plays for as long as the page is open. The background track fails the same way: it starts without being asked for, because the click that started it was for something else, and nothing on the page stops it.

## Who is affected

It also catches out anyone in a quiet office, a library or on a train, who often closes the tab rather than look for the source.

## What automation and AI miss

Browsers block sound that starts by itself on a first visit, so a tester who pastes the address into a fresh browser, and every headless audit, hears nothing. Chrome allows the same sound once the visitor has clicked something on the site, so someone who arrives from another of its pages hears the film at once. axe flags `autoplay` without `muted` or `controls`, but only for a person to confirm. It never sees sound that a script starts with `play()`. AI fixes often add `muted` and leave out any pause button, which silences the film but leaves a looping video that nobody can pause.

## Before: a film that starts with its sound on

```html
<section class="hero">
  <video src="our-story.mp4" autoplay loop playsinline></video>
  <h1>Roasted in small batches</h1>
</section>
```

The film starts with sound wherever the browser allows it, and plays on in a loop. With no controls and no other button, the only way to silence it is to leave the page or turn down the whole computer, screen reader included.

## Before: background music a script starts on the first click

```html
<audio id="music" src="cafe-jazz.mp3" loop></audio>
```

```js
// Music for the mood, started on the first click anywhere on the page.
document.addEventListener('click', () => {
  document.getElementById('music').play();
}, { once: true });
```

Browsers allow sound once the visitor has clicked, so this plays wherever that first click lands: on the menu, a product or an empty part of the page. The `<audio>` has no controls and the page has no button for it, so the only way to stop it is to leave. The fix is the first one below: play nothing until someone presses a button that says it plays music.

## After: the film waits for Play

```html
<section class="hero">
  <video id="film" src="our-story.mp4" poster="our-story.jpg"
    loop playsinline aria-hidden="true"></video>
  <h1>Roasted in small batches</h1>
  <button type="button" id="play" class="film-button">
    <svg aria-hidden="true" focusable="false"><use href="#icon-play"></use></svg>
    <span class="visually-hidden">Play film</span>
  </button>
</section>
<script>
  const film = document.getElementById('film');
  const play = document.getElementById('play');
  // The icon and the hidden name change together.
  const set = (button, icon, name) => {
    button.querySelector('use').setAttribute('href', icon);
    button.querySelector('.visually-hidden').textContent = name;
  };
  play.addEventListener('click', () => (film.paused ? film.play() : film.pause()));
  const show = () => set(play, film.paused ? '#icon-play' : '#icon-pause',
    film.paused ? 'Play film' : 'Pause film');
  ['play', 'pause'].forEach((type) => film.addEventListener(type, show));
</script>
```

Without `autoplay`, nothing plays until someone presses Play, so the page is quiet when it opens, and the poster gives the hero its picture until then. The button is a real `<button>`, so it works from the keyboard, and once the film plays the same button pauses it. Its icon and its hidden name come from the film's real state, so they stay right even if playback fails.

## After: the film starts muted, with Pause and Sound buttons

```html
<section class="hero">
  <video id="film" src="our-story.mp4" autoplay muted
    loop playsinline aria-hidden="true"></video>
  <h1>Roasted in small batches</h1>
  <button type="button" id="play" class="film-button">
    <svg aria-hidden="true" focusable="false"><use href="#icon-pause"></use></svg>
    <span class="visually-hidden">Pause film</span>
  </button>
  <button type="button" id="sound" class="film-button">
    <svg aria-hidden="true" focusable="false"><use href="#icon-muted"></use></svg>
    <span class="visually-hidden">Turn sound on</span>
  </button>
</section>
<script>
  const film = document.getElementById('film');
  const play = document.getElementById('play');
  const sound = document.getElementById('sound');
  // Each button's icon and hidden name change together.
  const set = (button, icon, name) => {
    button.querySelector('use').setAttribute('href', icon);
    button.querySelector('.visually-hidden').textContent = name;
  };
  play.addEventListener('click', () => (film.paused ? film.play() : film.pause()));
  sound.addEventListener('click', () => (film.muted = !film.muted));
  const show = () => {
    set(play, film.paused ? '#icon-play' : '#icon-pause', film.paused ? 'Play film' : 'Pause film');
    set(sound, film.muted ? '#icon-muted' : '#icon-sound', film.muted ? 'Turn sound on' : 'Turn sound off');
  };
  ['play', 'pause', 'volumechange'].forEach((type) => film.addEventListener(type, show));
  show();
</script>
```

The film moves from the start, but makes no sound until someone presses the sound button, so 1.4.2 is met. Keep the Pause button: a muted film that loops for more than five seconds still has to be pausable under 2.2.2 (Pause, Stop, Hide). The icons and names are set from the film's real state, because a browser can refuse to autoplay even a muted film, in a power-saving mode for example.

## Which option to use

Both pass 1.4.2. Waiting for Play costs nothing and is quiet for everyone: choose it when the film's point is what it says, or when it is not needed to make the page look finished. Starting muted keeps the moving picture the design was built around, and costs only the sound on arrival, which most browsers would have blocked anyway: choose it when the film is a moving backdrop.

## Implementation decisions

Hero films rarely show the browser's control bar, so the snippets give the film round icon buttons of its own. They are real `<button>` elements, so they need no ARIA role, and an icon alone is fine on screen as long as each of these holds:

- The name is in the code: hidden text such as "Pause film", or an `aria-label`, with the icon marked `aria-hidden="true"`. Hidden text is translated by browser translation tools, which do not always translate `aria-label`.
- The icon has 3:1 contrast against what is behind it (1.4.11). Over moving footage that means a solid dark disc under a white icon, which is what `.film-button` stands for here, alongside the usual `.visually-hidden` class.
- The button is at least 24 by 24 pixels (2.5.8); 44 is the usual size for a control floating over a film.
- The focus ring shows against the film.
- The icon and the name change together: the pause icon with "Pause film", the play icon with "Play film".

Play, pause and speaker icons are familiar enough to stand alone. A tooltip on hover and focus is optional, and if you add one it has to meet 1.4.13 (Content on Hover or Focus). 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.

Native `controls` pass too, and are the better choice for a film people sit and watch, such as a product video; they also bring a captions menu, which buttons of your own have to provide when the film has speech.

A banner film that only sets the mood needs nothing read about what it shows, so the snippets hide it with `aria-hidden="true"` and let the buttons say what they control. That works only while the film has no controls of its own: on a `<video controls>` it would hide focusable buttons, so give such a film a short name instead, such as `aria-label="Banner film"`. Either way, hiding a film from screen readers does nothing to its sound. Screen readers announce nothing when media starts, so the music simply plays over their speech.

A third way passes 1.4.2 on paper, but is not a fix to plan around: let the sound start by itself, with a "Turn off sound" button first on the page (technique G170). It falls short in three ways:

- Browsers block the very sound it protects on a first visit, so the design rarely plays as intended.
- Screen reader users still have to find the button while the music talks over their screen reader.
- It passes only while the button stays first, so a cookie banner or skip link added later quietly breaks it.

If a page really must start sound by itself, keep that button first, and in words rather than a lone icon.

A sound that ends within three seconds needs no control, which is why a short chime is fine. That is no way out for a long soundtrack: cutting it to three seconds changes the content more than adding a Play button does.

A volume slider inside the page counts as a control too, provided it changes only this page's sound. The system volume never counts, because it also controls the screen reader.

Sound that starts by itself does not only come from a `video` tag with `autoplay`. Each of these plays automatically in the sense of 1.4.2, and each takes the same fixes:

- Background music players, common on brand and restaurant sites: start them off, with a labelled button to turn them on.
- Embedded players from YouTube or Vimeo with autoplay switched on in their address: also switch on their mute option, and leave their controls showing.
- Scripts that call `play()` on the visitor's first click anywhere on the page: that click opened a menu or followed a link, so it was not a request for sound.

Some frameworks set `muted` as a property after the page loads instead of writing the attribute. Check the served HTML for the attribute, and check in the browser that the film is silent from the first frame.

Whichever fix you choose, a film with speech needs captions, and with buttons of your own, a way to turn them on. This banner has only music, so its snippets leave captions out.

## Verify the fix

1. In each browser's site settings, allow sound for the site (Chrome: Sound, Allow; Firefox: Autoplay, Allow Audio and Video; Safari: Auto-Play, Allow All Auto-Play). Then open the page from a link on another of its pages.
2. Wait three seconds without touching anything. No sound you did not start is playing.
3. Using only the keyboard, pause and mute every film or player on the page, and check that each button's icon and name change together.
4. With a screen reader running, open the page the same way. You can hear the page title and the first items it reads clearly.
5. Click around the page: open the menu, scroll and follow a product link. No sound starts unless the control you used says it plays sound.

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

## Limits

This is a starting pattern, not a guarantee for every application. Browsers change their autoplay rules, so a page that is silent today in one browser may not be tomorrow in another. Test the complete journey with your target browsers and assistive technology.
