Browsers ship a video player that is keyboard operable, announces its controls, and does its best with the viewer's own caption settings. Teams replace it because it looks different in Safari, and the replacement usually loses all three.
W3C's own advice about this is blunter than ours used to be. Rather than building a player, use one built for the job, and it names three that support audio description properly. AblePlayer, video.js, and OzPlayer. That is the recommendation from the standards body, and it is worth putting to a team before they start.
Captions, transcripts and audio description are covered in the media guide. This page is about the player around them.
What a Player Has to Do at All
W3C's list is short and it is a better starting brief than a pile of ARIA. The player has to be operable without a mouse, through a speech interface, when the page is zoomed larger, and with a screen reader running. The keyboard focus indicator has to be visible. Controls and information need clear labels. And text, controls and backgrounds need enough contrast between them.
Three things beyond that are what separate a good player from a compliant one. Playback speed control, which helps anybody who needs more processing time. Caption customization, meaning text style, size, color and position. And the ability to read the captions with a screen reader and a braille display, which is the only route into a video for somebody who is both deaf and blind.
Every Control Needs a Name and the Right Keys
Play, pause, mute, volume, the scrub bar, fullscreen, captions. Every one has to be reachable by Tab, announced as what it is, and operable by the keys its role promises. That last clause is where a blanket instruction goes wrong, because buttons and sliders answer to different keys and half your controls are sliders.
- A play button built from a
divwith a triangle in it announces as nothing. Use a realbutton, or you have failed 4.1.2 before anything else. A button answers Enter and Space. - A toggle has to report its state, and there are two ways to do it that must not be combined. Either
aria-pressedwith a label that never changes, or a label that changes with the state and noaria-pressedat all. Mute becoming Unmute and carryingaria-pressed="true"announces the state twice, in two different words. - The scrub bar is a slider, not a button. It needs arrow keys for single steps, and Home and End to jump to the start and the end, which are the two most useful keys on the control and the ones most often missing. Page Up and Page Down for larger jumps are a sensible extra.
- A slider needs four properties, not one.
aria-valuenow,aria-valuemin,aria-valuemax, and an accessible name. And it needsaria-valuetext, because a raw number is not a time. Without it a screen reader announces four hundred and thirty-seven. With it, seven minutes seventeen seconds. - Volume is a slider and mute is a separate toggle. Combining them into one control leaves somebody with no way to do one without the other.
Controls that appear on hover
A control bar that only shows on mouseover is unreachable for a keyboard user. Show it on focus as well, and keep it visible while anything inside it has focus. Content revealed on hover also owes the three conditions, which are dismissible, hoverable and persistent. And watch the opposite problem once the bar is always visible. A bar pinned to the bottom of the viewport is sticky content, and a focused control sitting completely underneath it is a 2.4.11 failure that only shows up when you tab back up the page.
Autoplay Is the First Thing to Remove
Audio that starts on its own and runs for more than three seconds needs handling under 1.4.2, at Level A. The rule offers two routes and most write-ups name one. Either a mechanism to pause or stop the audio, or a mechanism to control its volume independently of the system volume. One is enough. What does not count is telling somebody to mute their computer, because muting the system is not pausing or stopping anything.
The reason this one is Level A rather than a matter of taste is specific. Somebody using a screen reader cannot hear their own speech output over your video, so an autoplaying soundtrack does not annoy them, it removes their ability to use the page at all. W3C's own position goes past the rule as well. It discourages starting sounds automatically and encourages sound to start from an action the user took.
Moving video without sound engages 2.2.2 Pause, Stop, Hide instead, and with a condition worth knowing. The moving-content half applies where the motion starts automatically, lasts more than five seconds, and sits in parallel with other content. A video playing full screen with nothing beside it is not in parallel with anything, which is why W3C's own worked example of a full-screen advertisement needs no pause control at all.
Let People Change the Captions
Captions burned into the picture are pixels. They cannot be turned off, restyled, or read by a braille display, and that last one is the argument that matters, because it is the difference between a deafblind viewer reaching your video and not.
Be careful how you say the rest of it, though, because the obvious criteria do not apply. The resize rule excepts captions outright, and the text spacing rule excludes captions embedded directly into video frames by name. So burned-in captions do not fail either of those. Where they can fail is contrast, since captions inside the picture are images of text and that criterion covers images of text. The case for a real caption file is user need, and it is a strong one.
A caption file arrives through a track element, and the kind attribute decides what it is. captions for a transcription of dialogue, sound effects and musical cues, meant for when sound is unavailable. subtitles for dialogue translated into another language, meant for when sound is available but not understood, and that is the default value if you leave kind off. descriptions for audio descriptions of the visual content. Plus chapters and metadata. Files must be WebVTT, one track per element can carry default, and you cannot have two tracks sharing the same kind, language and label.
You can style the caption text yourself with the ::cue pseudo-element. What you should not promise a client is that a viewer's own operating system caption preferences will come through, because support for caption styling across browsers and players is inconsistent and sometimes unreliable, and most web video ends up in the player's default of white text in a black box. Treat inheriting the viewer's settings as a benefit of the native player rather than a guarantee of it.
One warning about a description track. kind="descriptions" is an advisory technique rather than a sufficient one, and browser support for it has historically been poor, which is exactly why W3C names specific players for this job. A description track needs a player that will actually play it. Extended audio description, where the video pauses so a longer description can fit, has only one markup-based route and support for it is very limited, which is the honest answer when a client asks about the AAA criterion.
Keyboard Shortcuts Need Care
Single-character shortcuts, such as K for play and F for fullscreen, are what 2.1.4 is about. The audience it protects is not people who find shortcuts inconvenient. It is speech input users, whose dictation lands in the page as keystrokes and fires your shortcuts by accident in the middle of a sentence.
The criterion gives three escapes and you only need one. Provide a way to turn the shortcut off. Provide a way to remap it so it includes a non-printable key such as Control or Alt. Or make it active only while the component it belongs to has focus. That third one is what most players should do, and the second one is the route almost nobody mentions.
The Thing Nobody Tests
Play the video with a screen reader running and your eyes shut. Then do the four things this page is actually about. Tab to every control and confirm you can tell what each one is. Move the scrub bar with the arrows and listen to what it says, then press Home and End. Toggle mute and listen to whether the state arrives once or twice. And Shift-Tab back up the page with the control bar showing, watching for a focused control that disappears underneath it.