Media Accessibility (Alt Text, Captions, Transcripts)
1. Alternative Text (alt attribute) Best Practices
| Image Type | Alt Text Pattern | Example | Rationale |
|---|---|---|---|
| Informative Image | Describe content/function | alt="Bar chart showing 40% increase in sales" |
Conveys essential information |
| Decorative Image | Empty alt attribute | alt="" or role="presentation" |
Screen reader ignores image |
| Functional Image | Describe action/purpose | alt="Search", alt="Submit form" |
Explains what happens when clicked |
| Image Link | Describe destination | alt="Go to home page" |
Where link takes user |
| Logo | Company/product name | alt="Acme Corporation" |
Identifies brand |
| Image with Adjacent Text | Complement, not duplicate | Text: "CEO Jane Smith" → alt="Portrait photo" |
Avoid redundancy |
| Complex Image/Chart | Short alt + long description | alt="Sales data" + aria-describedby |
Summary in alt, details elsewhere |
| Image Map | Alt on area elements | <area alt="California"> |
Each clickable region labeled |
Example: Alt text patterns for different scenarios
<!-- Informative image -->
<img src="chart.png" alt="Quarterly sales increased 35% from Q1 to Q2">
<!-- Decorative image (empty alt) -->
<img src="divider.png" alt="">
<img src="pattern.png" role="presentation">
<!-- Functional image (button/link) -->
<button>
<img src="trash.png" alt="Delete item">
</button>
<a href="/">
<img src="logo.png" alt="Return to homepage">
</a>
<!-- Logo in header -->
<header>
<a href="/">
<img src="logo.svg" alt="Acme Corp">
</a>
</header>
<!-- Image with caption (don't duplicate) -->
<figure>
<img src="sunset.jpg" alt="Golden sunset over mountain range">
<figcaption>
Sunset at Rocky Mountain National Park, June 2024
</figcaption>
</figure>
<!-- Complex image with long description -->
<img
src="org-chart.png"
alt="Company organizational chart"
aria-describedby="chart-desc">
<div id="chart-desc">
<p>Hierarchical structure showing CEO at top, reporting to
three Vice Presidents for Engineering, Sales, and Marketing...</p>
</div>
<!-- Image map -->
<img src="map.png" alt="United States map" usemap="#us-map">
<map name="us-map">
<area shape="poly" coords="..." href="/ca" alt="California">
<area shape="poly" coords="..." href="/tx" alt="Texas">
</map>
<!-- Background image with CSS (provide text alternative) -->
<div class="hero" style="background-image: url('hero.jpg')"
role="img" aria-label="Team collaborating in modern office">
</div>
| Alt Text Guidelines | Do | Don't |
|---|---|---|
| Length | Concise (typically <150 chars) | Write lengthy paragraphs in alt |
| Phrases to Avoid | Start directly with content | "Image of...", "Picture of...", "Graphic of..." |
| Punctuation | Use proper punctuation | Omit periods or use all caps |
| Context | Match surrounding content context | Provide irrelevant details |
| File Names | Write meaningful description | Use file names: "IMG_1234.jpg" |
| Redundancy | Complement adjacent text | Duplicate nearby text verbatim |
| Decorative | Use alt="" |
Omit alt attribute entirely |
Warning: Missing alt attributes cause screen readers to announce the file name. Always include
alt="" for decorative images rather than omitting the attribute.
2. Video Captions (track, kind="captions") and Transcripts
| Caption Type | Format | Use Case | WCAG Level |
|---|---|---|---|
| Closed Captions (CC) | WebVTT (.vtt), SRT (.srt) | User can toggle on/off - includes dialogue + sounds | AA (prerecorded) |
| Open Captions | Burned into video | Always visible, cannot be disabled | AA (prerecorded) |
| Subtitles | WebVTT (.vtt) | Translation for different languages | Not required |
| Full Transcript | HTML text | Complete text of all audio + visual descriptions | AAA (recommended) |
| Live Captions | Real-time text stream | Live broadcasts, meetings | AA (live content) |
Example: Video with captions and transcript
<!-- Video with multiple caption tracks -->
<video controls width="640" height="360">
<source src="video.mp4" type="video/mp4">
<source src="video.webm" type="video/webm">
<!-- English captions (default) -->
<track
kind="captions"
src="captions-en.vtt"
srclang="en"
label="English"
default>
<!-- Spanish subtitles -->
<track
kind="subtitles"
src="subtitles-es.vtt"
srclang="es"
label="Español">
<!-- Audio description track -->
<track
kind="descriptions"
src="descriptions.vtt"
srclang="en"
label="Audio Descriptions">
<!-- Fallback for browsers without video support -->
<p>Your browser doesn't support HTML5 video.
<a href="video.mp4">Download the video</a> or
<a href="#transcript">read the transcript</a>.
</p>
</video>
<!-- Transcript section -->
<details id="transcript">
<summary>Video Transcript</summary>
<div>
<h3>Introduction to Web Accessibility</h3>
<p><strong>[00:00]</strong></p>
<p><strong>Narrator:</strong> Welcome to our guide on web accessibility.</p>
<p><strong>[00:05] [Background music plays]</strong></p>
<p><strong>Narrator:</strong> In this video, we'll explore why accessibility matters.</p>
<p><strong>[00:10] [Visual: Statistics chart appears]</strong></p>
<p><strong>Narrator:</strong> Over 1 billion people worldwide have disabilities.</p>
<p><strong>[End of transcript]</strong></p>
</div>
</details>
Example: WebVTT caption file format
WEBVTT
NOTE This is a caption file for accessibility demo
00:00:00.000 --> 00:00:03.000
Welcome to our guide on web accessibility.
00:00:03.500 --> 00:00:07.000
[Background music plays]
00:00:07.000 --> 00:00:11.000
In this video, we'll explore why accessibility matters.
00:00:11.500 --> 00:00:15.000
<v Narrator>Over 1 billion people worldwide have disabilities.</v>
00:00:15.500 --> 00:00:19.000
[Upbeat music]
Making websites accessible benefits everyone.
NOTE You can add speaker names with <v Speaker Name>
NOTE You can add positioning with align:start, align:middle, align:end
| Caption Content Requirements | Include | Example |
|---|---|---|
| Dialogue | All spoken words | "Welcome to our presentation" |
| Speaker Identification | Who is speaking (when not obvious) | "<v Sarah>Let me explain...</v>" |
| Sound Effects | Important non-speech sounds | "[Door slams]", "[Phone rings]" |
| Music Cues | Mood-setting or significant music | "[Suspenseful music]", "[♪ Jazz playing ♪]" |
| Tone/Manner | How something is said (when important) | "[Sarcastically] That's just great." |
| Off-Screen Audio | Sounds from outside frame | "[Voice from hallway] Is anyone there?" |
Note: Captions and subtitles are different. Captions include
dialogue + sound effects for deaf/hard-of-hearing. Subtitles are translations of
dialogue only.
3. Audio Descriptions (kind="descriptions") Implementation
| Audio Description Type | Implementation | Use Case | WCAG Level |
|---|---|---|---|
| Standard Audio Description | Narration fits in existing pauses | Videos with natural dialogue breaks | AA (prerecorded) |
| Extended Audio Description | Video pauses for longer descriptions | Videos without adequate pauses | AAA |
| Descriptive Transcript | Text with visual descriptions | Alternative to audio description | AAA |
| Text Track (VTT descriptions) | <track kind="descriptions"> |
Browser-supported descriptions | Modern approach |
| Separate Audio Track | Alternate video file with descriptions | When track element not supported | Fallback option |
Example: Video with audio descriptions
<!-- Video with audio description track -->
<video id="described-video" controls>
<source src="presentation.mp4" type="video/mp4">
<!-- Captions -->
<track
kind="captions"
src="captions.vtt"
srclang="en"
label="English Captions"
default>
<!-- Audio descriptions -->
<track
kind="descriptions"
src="audio-descriptions.vtt"
srclang="en"
label="Audio Descriptions">
</video>
<!-- Toggle for audio descriptions -->
<button onclick="toggleDescriptions()">
Toggle Audio Descriptions
</button>
<script>
const video = document.getElementById('described-video');
const descTrack = video.textTracks[1]; // descriptions track
function toggleDescriptions() {
if (descTrack.mode === 'showing') {
descTrack.mode = 'hidden';
} else {
descTrack.mode = 'showing';
}
}
// Listen for description cues
descTrack.addEventListener('cuechange', () => {
const cue = descTrack.activeCues[0];
if (cue) {
// Optionally announce via screen reader
announceToScreenReader(cue.text);
}
});
function announceToScreenReader(text) {
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', 'polite');
announcement.className = 'sr-only';
announcement.textContent = text;
document.body.appendChild(announcement);
setTimeout(() => announcement.remove(), 1000);
}
</script>
Example: Audio description VTT file
WEBVTT
NOTE Audio descriptions for presentation video
00:00:05.000 --> 00:00:08.000
A woman in a blue suit stands at a podium.
00:00:15.000 --> 00:00:18.000
She gestures to a graph showing upward trending data.
00:00:28.000 --> 00:00:32.000
The camera pans to audience members taking notes.
00:00:45.000 --> 00:00:49.000
A pie chart appears on screen showing market distribution.
00:01:02.000 --> 00:01:06.000
The speaker smiles and nods to acknowledge applause.
| What to Describe | Priority | Example Description |
|---|---|---|
| Actions | High | "Sarah picks up the phone" |
| Settings/Scenes | High | "Modern office with glass walls" |
| Characters/People | High | "A man in his 40s with gray hair" |
| On-Screen Text | High | "Title card: Five Years Later" |
| Facial Expressions | Medium | "She frowns and looks away" |
| Clothing/Appearance | Medium | "Wearing a red dress and pearl necklace" |
| Atmosphere/Mood | Low | "The dimly lit room creates tension" |
Warning: Audio descriptions must fit between dialogue/narration without overlapping. If there
isn't enough time, consider extended audio descriptions (which pause video) or detailed transcripts.
| Media Accessibility Checklist | Video | Audio Only |
|---|---|---|
| Captions/Subtitles | ✓ Required (AA) | N/A |
| Audio Descriptions | ✓ Required (AA) | N/A |
| Transcript | Recommended (AAA) | ✓ Required (AA) |
| Keyboard Controls | ✓ Required | ✓ Required |
| Pause/Stop | ✓ Required | ✓ Required |
| Volume Control | ✓ Required | ✓ Required |
| No Auto-play | ✓ Best practice | ✓ Best practice |
4. Complex Images and Data Visualization (longdesc, aria-describedby)
| Visualization Type | Accessibility Strategy | Implementation |
|---|---|---|
| Charts/Graphs | Short alt + detailed description + data table | aria-describedby + <table> |
| Infographics | Structured HTML alternative | Headings, lists, and semantic markup |
| Diagrams | Long description or labeled version | <details> or separate page |
| Maps | Interactive alternative or text description | List of locations with links/coordinates |
| Flowcharts | Text-based outline of flow | Ordered/nested lists showing steps |
| Interactive Dashboards | Keyboard navigation + ARIA live regions | Announce data updates, allow filtering |
Example: Chart with multiple accessibility layers
<!-- Complex chart with comprehensive accessibility -->
<figure>
<img
src="sales-chart.png"
alt="Bar chart: Monthly sales for 2024"
aria-describedby="chart-summary chart-data">
<figcaption id="chart-summary">
Sales increased steadily throughout 2024, starting at $50K in January
and reaching $120K by December, with notable spikes in June ($95K)
and November ($115K).
</figcaption>
<!-- Detailed data table (can be in details/summary) -->
<details>
<summary>View detailed data table</summary>
<table id="chart-data">
<caption>Monthly Sales Data 2024</caption>
<thead>
<tr>
<th scope="col">Month</th>
<th scope="col">Sales</th>
<th scope="col">Change</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">January</th>
<td>$50,000</td>
<td>-</td>
</tr>
<tr>
<th scope="row">February</th>
<td>$58,000</td>
<td>+16%</td>
</tr>
<tr>
<th scope="row">March</th>
<td>$65,000</td>
<td>+12%</td>
</tr>
<!-- More rows... -->
</tbody>
</table>
</details>
</figure>
Example: Accessible SVG chart with ARIA
<!-- Accessible SVG chart -->
<svg
role="img"
aria-labelledby="chart-title chart-desc"
width="600"
height="400">
<title id="chart-title">Quarterly Revenue Growth</title>
<desc id="chart-desc">
Bar chart showing revenue growth across four quarters.
Q1: $2.5M, Q2: $3.1M, Q3: $3.8M, Q4: $4.2M.
Revenue increased 68% from Q1 to Q4.
</desc>
<!-- Chart elements -->
<g role="list" aria-label="Quarterly data">
<rect role="listitem" aria-label="Q1: $2.5 million"
x="50" y="200" width="100" height="150" fill="#0066cc"></rect>
<rect role="listitem" aria-label="Q2: $3.1 million"
x="175" y="150" width="100" height="200" fill="#0066cc"></rect>
<rect role="listitem" aria-label="Q3: $3.8 million"
x="300" y="100" width="100" height="250" fill="#0066cc"></rect>
<rect role="listitem" aria-label="Q4: $4.2 million"
x="425" y="50" width="100" height="300" fill="#0066cc"></rect>
</g>
<!-- Decorative elements hidden from screen readers -->
<g aria-hidden="true">
<!-- Axis lines, grid, etc. -->
</g>
</svg>
| Data Viz Best Practice | Why | Implementation |
|---|---|---|
| Provide Data Table | Gives precise values screen readers can navigate | Hidden by default, shown in <details> |
| Short + Long Description | Alt gives overview, long desc gives details | alt + aria-describedby |
| Sufficient Color Contrast | Users with low vision need to see data points | 4.5:1 minimum for data elements |
| Don't Rely on Color Alone | Color blind users can't distinguish colors | Use patterns, labels, shapes too |
| Keyboard Navigable | Users need to explore data without mouse | Focus indicators on data points |
| Announce Dynamic Changes | Screen reader users need update notifications | aria-live regions for data updates |
Note: For complex interactive visualizations (D3.js, Chart.js), consider using accessible
charting libraries like Highcharts (with accessibility module) or provide comprehensive data tables as
alternatives.
5. Icon and SVG Accessibility (aria-hidden, role="img")
| Icon Type | Treatment | Code Pattern | Screen Reader Output |
|---|---|---|---|
| Decorative Icon | Hide from AT | aria-hidden="true" |
Ignored |
| Informative Icon | Provide text alternative | role="img" + aria-label |
Announces label |
| Icon in Button | Label button, hide icon | aria-label on button, aria-hidden on icon |
Announces button label |
| Icon with Adjacent Text | Hide icon from AT | aria-hidden="true" on icon |
Announces text only |
| Icon Font | Add text or ARIA label | <span aria-label="Save"><i>💾</i></span> |
Announces label |
| SVG Icon | Use <title> or aria-label |
<svg><title>Save</title></svg> |
Announces title |
Example: Icon accessibility patterns
<!-- Decorative icon with text -->
<button>
<svg aria-hidden="true" class="icon">...</svg>
Save Document
</button>
<!-- Icon-only button (needs label) -->
<button aria-label="Delete item">
<svg aria-hidden="true">
<use href="#trash-icon"></use>
</svg>
</button>
<!-- Informative icon (standalone) -->
<svg role="img" aria-label="Warning">
<use href="#warning-icon"></use>
</svg>
<!-- SVG with title and desc -->
<svg role="img" aria-labelledby="save-title save-desc">
<title id="save-title">Save</title>
<desc id="save-desc">Floppy disk icon</desc>
<path d="M..."></path>
</svg>
<!-- Font icon (FontAwesome, Material Icons) -->
<span class="icon-wrapper">
<i class="fas fa-home" aria-hidden="true"></i>
<span class="sr-only">Home</span>
</span>
<!-- Emoji icons (problematic - provide text alternative) -->
<span role="img" aria-label="thumbs up">👍</span>
<!-- Status icon with text -->
<span class="status">
<svg aria-hidden="true" class="icon-success">...</svg>
<span>Successfully saved</span>
</span>
<!-- Icon link -->
<a href="/settings" aria-label="Settings">
<svg aria-hidden="true">
<use href="#gear-icon"></use>
</svg>
</a>
<!-- Icon sprite definition (hidden) -->
<svg style="display: none;">
<symbol id="trash-icon" viewBox="0 0 24 24">
<path d="M..."></path>
</symbol>
<symbol id="warning-icon" viewBox="0 0 24 24">
<path d="M..."></path>
</symbol>
</svg>
| SVG Accessibility Technique | Code | Use Case |
|---|---|---|
| <title> element | <svg><title>Icon name</title>...</svg> |
Simple SVG with name only |
| <title> + <desc> | <title>...</title><desc>...</desc> |
SVG needing detailed description |
| role="img" | <svg role="img"> |
Ensure SVG treated as image |
| aria-labelledby | aria-labelledby="title-id desc-id" |
Link to title and desc elements |
| aria-label | <svg aria-label="Icon name"> |
Alternative to title element |
| aria-hidden="true" | <svg aria-hidden="true"> |
Decorative SVG |
| focusable="false" | <svg focusable="false"> |
Prevent focus in IE/Edge |
Warning: Never use CSS content property for informative icons - screen readers may not announce
them. Always use proper HTML with ARIA labels or semantic
<title> elements.
| Icon Library | Accessibility Approach | Example |
|---|---|---|
| Font Awesome | Use aria-hidden + text or title |
<i class="fas fa-user" aria-hidden="true"></i><span class="sr-only">User</span>
|
| Material Icons | Add aria-label or adjacent text |
<span class="material-icons" aria-label="delete">delete</span> |
| Heroicons (SVG) | Use aria-hidden on icon, label on parent |
<button aria-label="Menu"><svg aria-hidden="true">...</svg></button>
|
| Emoji | Wrap in role="img" with aria-label |
<span role="img" aria-label="celebration">🎉</span> |
6. Multimedia Controls and Interfaces
| Control | Keyboard Shortcut | ARIA Attributes | Required |
|---|---|---|---|
| Play/Pause | Space or K | aria-label="Play" or "Pause" |
✓ Yes |
| Volume | ↑↓ or M (mute) | role="slider", aria-valuenow |
✓ Yes |
| Seek/Scrub | ←→ (5s), J/L (10s) | role="slider", aria-valuetext |
✓ Yes |
| Fullscreen | F | aria-label="Enter fullscreen" |
Recommended |
| Captions Toggle | C | aria-pressed for toggle state |
✓ Yes (if captions available) |
| Playback Speed | Shift+> / Shift+< | aria-label with current speed |
Recommended |
| Audio Descriptions | D | aria-pressed for toggle |
If descriptions available |
Example: Custom accessible video player
<div class="video-player" role="region" aria-label="Video player">
<video id="my-video">
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" default>
</video>
<div class="controls" role="group" aria-label="Video controls">
<!-- Play/Pause -->
<button
id="play-pause"
aria-label="Play"
class="control-btn">
<svg aria-hidden="true">
<use href="#play-icon"></use>
</svg>
</button>
<!-- Progress bar -->
<div class="progress-wrapper">
<input
type="range"
id="progress"
role="slider"
aria-label="Video progress"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="0"
aria-valuetext="0:00 of 5:30"
min="0"
max="100"
value="0">
<div class="time-display" aria-live="off">
<span id="current-time">0:00</span> /
<span id="duration">5:30</span>
</div>
</div>
<!-- Volume control -->
<button
id="mute"
aria-label="Mute"
aria-pressed="false"
class="control-btn">
<svg aria-hidden="true">
<use href="#volume-icon"></use>
</svg>
</button>
<input
type="range"
id="volume"
role="slider"
aria-label="Volume"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="100"
aria-valuetext="100%"
min="0"
max="100"
value="100">
<!-- Captions toggle -->
<button
id="captions"
aria-label="Captions"
aria-pressed="true"
class="control-btn">
<svg aria-hidden="true">
<use href="#cc-icon"></use>
</svg>
</button>
<!-- Fullscreen -->
<button
id="fullscreen"
aria-label="Enter fullscreen"
class="control-btn">
<svg aria-hidden="true">
<use href="#fullscreen-icon"></use>
</svg>
</button>
</div>
</div>
<script>
class AccessibleVideoPlayer {
constructor(videoElement) {
this.video = videoElement;
this.playPauseBtn = document.getElementById('play-pause');
this.progressBar = document.getElementById('progress');
this.muteBtn = document.getElementById('mute');
this.volumeBar = document.getElementById('volume');
this.captionsBtn = document.getElementById('captions');
this.fullscreenBtn = document.getElementById('fullscreen');
this.setupEventListeners();
this.setupKeyboardShortcuts();
}
setupEventListeners() {
// Play/Pause
this.playPauseBtn.addEventListener('click', () => this.togglePlay());
this.video.addEventListener('play', () => this.updatePlayButton(true));
this.video.addEventListener('pause', () => this.updatePlayButton(false));
// Progress
this.video.addEventListener('timeupdate', () => this.updateProgress());
this.progressBar.addEventListener('input', (e) => this.seek(e.target.value));
// Volume
this.muteBtn.addEventListener('click', () => this.toggleMute());
this.volumeBar.addEventListener('input', (e) => this.setVolume(e.target.value));
// Captions
this.captionsBtn.addEventListener('click', () => this.toggleCaptions());
// Fullscreen
this.fullscreenBtn.addEventListener('click', () => this.toggleFullscreen());
}
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Only handle if video player has focus
if (!this.video.closest('.video-player').contains(document.activeElement)) {
return;
}
switch(e.key) {
case ' ':
case 'k':
e.preventDefault();
this.togglePlay();
break;
case 'ArrowLeft':
e.preventDefault();
this.skip(-5);
break;
case 'ArrowRight':
e.preventDefault();
this.skip(5);
break;
case 'ArrowUp':
e.preventDefault();
this.changeVolume(0.1);
break;
case 'ArrowDown':
e.preventDefault();
this.changeVolume(-0.1);
break;
case 'm':
e.preventDefault();
this.toggleMute();
break;
case 'c':
e.preventDefault();
this.toggleCaptions();
break;
case 'f':
e.preventDefault();
this.toggleFullscreen();
break;
}
});
}
togglePlay() {
if (this.video.paused) {
this.video.play();
} else {
this.video.pause();
}
}
updatePlayButton(isPlaying) {
this.playPauseBtn.setAttribute('aria-label', isPlaying ? 'Pause' : 'Play');
}
updateProgress() {
const percent = (this.video.currentTime / this.video.duration) * 100;
this.progressBar.value = percent;
const currentTime = this.formatTime(this.video.currentTime);
const duration = this.formatTime(this.video.duration);
this.progressBar.setAttribute('aria-valuenow', percent);
this.progressBar.setAttribute('aria-valuetext', `${currentTime} of ${duration}`);
document.getElementById('current-time').textContent = currentTime;
document.getElementById('duration').textContent = duration;
}
seek(percent) {
this.video.currentTime = (percent / 100) * this.video.duration;
}
skip(seconds) {
this.video.currentTime += seconds;
}
toggleMute() {
this.video.muted = !this.video.muted;
this.muteBtn.setAttribute('aria-pressed', this.video.muted);
this.muteBtn.setAttribute('aria-label', this.video.muted ? 'Unmute' : 'Mute');
}
setVolume(value) {
this.video.volume = value / 100;
this.volumeBar.setAttribute('aria-valuenow', value);
this.volumeBar.setAttribute('aria-valuetext', `${value}%`);
}
changeVolume(delta) {
const newVolume = Math.max(0, Math.min(1, this.video.volume + delta));
this.video.volume = newVolume;
this.volumeBar.value = newVolume * 100;
this.setVolume(newVolume * 100);
}
toggleCaptions() {
const track = this.video.textTracks[0];
if (track.mode === 'showing') {
track.mode = 'hidden';
this.captionsBtn.setAttribute('aria-pressed', 'false');
} else {
track.mode = 'showing';
this.captionsBtn.setAttribute('aria-pressed', 'true');
}
}
toggleFullscreen() {
if (document.fullscreenElement) {
document.exitFullscreen();
this.fullscreenBtn.setAttribute('aria-label', 'Enter fullscreen');
} else {
this.video.closest('.video-player').requestFullscreen();
this.fullscreenBtn.setAttribute('aria-label', 'Exit fullscreen');
}
}
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
}
// Initialize player
const player = new AccessibleVideoPlayer(document.getElementById('my-video'));
</script>
| Player Best Practice | Implementation | WCAG Criteria |
|---|---|---|
| Keyboard Accessible | All controls operable via keyboard | 2.1.1 Keyboard |
| Visible Focus | Clear focus indicators on all controls | 2.4.7 Focus Visible |
| Labeled Controls | aria-label on all buttons |
4.1.2 Name, Role, Value |
| State Announcements | Update ARIA attributes dynamically | 4.1.2 Name, Role, Value |
| No Auto-play | User initiates playback | 1.4.2 Audio Control |
| Pause Control | Always provide pause button | 2.2.2 Pause, Stop, Hide |
| Touch Targets ≥44px | Buttons large enough for mobile | 2.5.5 Target Size (AAA) |
Note: Use native
<video controls> when possible - browsers provide
accessible controls by default. Only build custom controls when design absolutely requires it.
Media and Content Accessibility Quick Reference
- All images need
altattribute - usealt=""for decorative images - Describe content/function in alt text, not just "image of..." - keep under 150 characters
- Videos require captions (AA) and audio descriptions (AA) for WCAG compliance
- Provide transcripts for all media - helps SEO and provides searchable alternative
- Use WebVTT format for captions: include dialogue, speaker ID, and sound effects
- Complex charts need: short alt + detailed description + data table alternative
- SVG icons: use
aria-hidden="true"for decorative,role="img"+aria-labelfor informative - Icon-only buttons must have
aria-label- hide icon witharia-hidden - Custom video players need keyboard shortcuts (Space, arrows, M, F, C) and ARIA labels
- Never auto-play media with sound - provide pause/stop controls within 3 clicks/taps