Requires audio description for all prerecorded video in synchronized media, narrating the important visual details that the main soundtrack does not cover.
Audio description is a second narration that tells blind and low-vision viewers what the picture shows when the main soundtrack does not: who walks into the room, what a chart looks like, which button is clicked, what text appears on the screen.
This criterion requires audio description for all prerecorded video in synchronized media, that is, prerecorded video with a soundtrack. It is the Level AA counterpart of 1.2.3. At Level A you may choose between audio description and a text media alternative. At Level AA, audio description itself is required, so a descriptive transcript alone is no longer enough.
Blind and low-vision users follow the visual part of the video through audio description. It also helps people who listen without watching (while cooking or driving), people with cognitive or attention differences who benefit from a narrated picture, and people who are new to the topic and cannot tell what the picture is showing.
The European Accessibility Act (Directive (EU) 2019/882) applies to in-scope products and services placed on the market after 28 June 2025. Recital 47 names four principles for websites and mobile applications: perceivable, operable, understandable and robust. Audio description gives non-visual access to what a video shows, and is part of "perceivable".
The technical counterpart is the standard EN 301 549, which incorporates WCAG 1.2.5 by reference and turns it into separate clauses for each kind of ICT. In addition, clause 7 of the standard sets requirements for the video players themselves.
| Clause | Type of ICT | What it requires |
|---|---|---|
| 9.1.2.5 | Web pages | Web content must satisfy WCAG success criterion 1.2.5. |
| 10.1.2.5 | Non-web documents (PDF, Office files) | The same criterion applies to video embedded in documents. |
| 11.1.2.5 | Non-web software (native mobile apps) | Software that supports screen readers must satisfy the criterion. |
| 7.2 | Video technologies (players and software that show video with sound) | The player must be able to play audio description, keep it synchronized and preserve it when the video is passed on. It must also give users a way to turn it on. |
Clause numbers follow the "9. + WCAG number" pattern of EN 301 549. In V3.2.1 the software clause is divided into further subclauses for open and closed functionality, and clause 7.2 and 7.3 are divided into sub-requirements for playback, synchronization, preservation and user controls. Check the exact numbers in the standard when you cite them.
Standard version: EN 301 549 V4.1.1 (September 2026) is based on WCAG 2.2 and is the first version prepared specifically for the EAA. It has not yet been cited in the Official Journal of the European Union, so for now it is best treated as a voluntary technical route to the EAA's requirements. Once cited, it will give a presumption of conformity. The text of criterion 1.2.5 is the same in WCAG 2.1 and 2.2. It is a Level AA criterion.
A note on EAA scope: Article 2(4) excludes pre-recorded time-based media published before 28 June 2025, and third-party content that the business neither funds, develops nor controls. Videos you publish after that date, and older videos you republish or substantially change, are not covered by the exclusion.
Directive (EU) 2019/882, EUR-Lex, opens in a new tab
A button with a name and a state lets the user pick the version with audio description, and the video keeps its position when the version changes.
<!-- A described version exists but nothing links to it -->
<video controls src="tour.mp4"></video><video id="tour" controls src="tour.mp4"></video>
<button type="button" id="ad" aria-pressed="false">
Audio description
</button>
<script>
const video = document.getElementById("tour");
const btn = document.getElementById("ad");
btn.addEventListener("click", () => {
const on = btn.getAttribute("aria-pressed") !== "true";
const time = video.currentTime;
const wasPlaying = !video.paused;
video.src = on ? "tour-described.mp4" : "tour.mp4";
video.addEventListener("loadedmetadata", () => {
video.currentTime = time;
if (wasPlaying) video.play();
}, { once: true });
btn.setAttribute("aria-pressed", String(on));
});
</script>Many platforms cannot play two audio tracks. Upload the described version as its own video and link to it next to the standard version.
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
title="Product tour"></iframe><iframe src="https://www.youtube.com/embed/VIDEO_ID"
title="Product tour"></iframe>
<p><a href="https://www.youtube.com/watch?v=DESCRIBED_ID"
target="_blank" rel="noopener noreferrer">Product tour
with audio description<span class="sr-only">, opens in a
new tab</span></a></p>Write the description as a timed script, so the narrator and the editor know where each line goes. Describe what is important, in the present tense, and do not interpret.
00:12 Anna looks sad because the project failed.
00:20 Many things happen on the screen.00:12 Anna closes her laptop and looks out of the window.
00:20 A red banner appears on the dashboard:
"Deployment failed".
00:31 Tom opens the logs. Three lines are highlighted
in yellow.A description that talks over dialogue is hard to follow. Check the pauses in the main soundtrack before you write.
Main audio (00:40 to 00:48): "Next, we look at the report."
Description (00:41 to 00:49): "Anna opens a spreadsheet with
four tabs, a pie chart on the right and a filter at the top."Main audio (00:40 to 00:48): "Next, we look at the report."
Pause: 00:48 to 00:53
Description (00:48 to 00:53): "A spreadsheet with a pie
chart on the right."Users must be able to find the described version without guessing. Put the words "audio description" in the link text, the button label or the video title.
<a href="/videos/tour-v2">Version 2</a><a href="/videos/tour-described">Product tour
(with audio description)</a>The same rule applies in native mobile apps. Offer a control that switches to the version with audio description, and keep the playback position when the version changes.
import SwiftUI
import AVKit
struct DescribedVideoView: View {
private let normalURL = URL(string: "https://example.com/tour.m3u8")!
private let describedURL = URL(string: "https://example.com/tour-described.m3u8")!
@State private var player: AVPlayer
@State private var described = false
init() {
_player = State(initialValue: AVPlayer(
url: URL(string: "https://example.com/tour.m3u8")!
))
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
VideoPlayer(player: player)
.frame(height: 220)
Toggle("Audio description", isOn: $described)
.onChange(of: described) { _, isOn in
switchVersion(described: isOn)
}
}
}
private func switchVersion(described: Bool) {
let time = player.currentTime()
let wasPlaying = player.rate > 0
player.replaceCurrentItem(
with: AVPlayerItem(url: described ? describedURL : normalURL)
)
player.seek(to: time)
if wasPlaying { player.play() }
}
}import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
@Composable
fun DescribedVideo(player: ExoPlayer) {
var described by remember { mutableStateOf(false) }
Column {
AndroidView(factory = { context ->
PlayerView(context).apply { this.player = player }
})
Row {
Text("Audio description")
Switch(
checked = described,
onCheckedChange = { isOn ->
described = isOn
val position = player.currentPosition
val wasPlaying = player.isPlaying
val url = if (isOn)
"https://example.com/tour-described.mp4"
else
"https://example.com/tour.mp4"
player.setMediaItem(MediaItem.fromUri(url), position)
player.prepare()
if (wasPlaying) player.play()
}
)
}
}
}