packages/compliance/a11y/player/RGAA-2026-04-14.md
Review date: 14.04.2026
Standard referenced: RGAA 4.1.2 (maps to WCAG 2.1 AA / EN 301 549)
Component under review: <Player> from @remotion/player
Instance tested: Demo Player on https://www.remotion.dev/player ("Your favorite color is …" composition)
Test environment: Chrome + live DOM inspection
Method: DOM / accessibility-tree inspection via DevTools + scripted queries; manual focus/label checks.
Scope note: This review covers the Player component (container semantics, play/pause, mute, seek, volume, fullscreen, time display). It does not cover page-level issues on /player (marketing-page headings, surrounding demo "color-selection controls", duplicate link text, etc.) — those belong to the page, not the component.
| Area | Finding |
|---|---|
| Play / Pause button | Native <button type="button">; accessible name toggles between aria-label="Play video" and aria-label="Pause video" to reflect state. Focusable via Tab. |
| Mute button | Native <button type="button"> with aria-label="Mute sound" and matching title. Focusable via Tab. |
| Fullscreen button | Native <button type="button"> with aria-label="Enter Fullscreen" and matching title. Focusable via Tab. |
| Time display text | Rendered as real text in the DOM ("0:10 / 0:11") — readable by screen readers when reached. |
| Composition render surface | Compositions render to real DOM (no <canvas>/<video>), so text inside a composition (e.g. "Your favorite color is …") is announceable by screen readers. |
Decorative <audio> tags | 5 preload <audio> elements live in the DOM but are display: none, so they do not appear in the visual tab order in practice. |
| # | Criterion | Status | Observation |
|---|---|---|---|
| 9.2 | Are landmarks / regions present and relevant? | NC | .__remotion-player wrapper has no role, no aria-label, no aria-roledescription. Screen-reader users meet an unlabelled group of buttons with no indication that this is a video player. Add role="region" + aria-label="Video player" (or aria-roledescription="video player"). |
| # | Criterion | Status | Observation |
|---|---|---|---|
| 4.2 | Prerecorded synchronized media has captions? | NC | <Player> does not render a <video>/<track> hierarchy and exposes no captions/subtitles mechanism in the component API. For compositions with spoken dialogue, captions are impossible without consumer code. Add a first-class captions / <track>-equivalent surface. |
| 4.3 | Audio description or text alternative | NC | No mechanism in the component for audio descriptions or a synchronized text alternative. |
| 4.12 | Are all media-player controls correctly labeled? | C* | Play/Pause, Mute, Fullscreen each have aria-label and title. But the Mute button label does not toggle to "Unmute sound" after activation, and carries no aria-pressed — see 7.1. |
| 4.13 | Pause / stop / hide for auto-playing media | NC | A <Player autoPlay loop> instance has no built-in pause/stop/hide for users with prefers-reduced-motion. Documentation / API gap. |
| # | Criterion | Status | Observation |
|---|---|---|---|
| 7.1 | Is each script compatible with assistive technologies? | NC | Seek bar is built from plain nested <div> elements (five divs in a 448 px stack, heights 5/8/13 px). No role="slider", no aria-valuemin / aria-valuemax / aria-valuenow, no aria-label. Screen-reader users cannot perceive progress nor a seek affordance. Volume slider (the 65 px <div> next to the mute button) is likewise an unannotated <div>. Mute button has no aria-pressed and the label does not toggle ("Mute sound" stays "Mute sound"); muted state is not programmatically exposed. |
| 7.2 | Is each script controllable by keyboard? | NC | Seek bar is not keyboard-operable (not in tab order; no key handlers). There is no way to scrub forward/back with the keyboard. Volume slider is not keyboard-operable. The component provides no media keyboard shortcuts on the player root: Space/K, ←/→, ↑/↓, M, F all do nothing. |
| 7.5 | Are status messages correctly rendered by assistive technologies? | NC | Time display ("0:10 / 0:11") updates silently each frame — no role="timer", no aria-live. Mute state changes are not announced. Add aria-pressed on mute (or a polite live region) and expose current time via aria-valuetext on the seek slider. |
| # | Criterion | Status | Observation |
|---|---|---|---|
| 10.7 | Focus visible | C | Keyboard-focused Play/Pause, Mute, and Fullscreen do show a visible focus ring (screenshot confirmed: blue outline on the Pause button). The author CSS sets outline-style: none on the :focus state, but Chrome's default :focus-visible UA style restores a ring for keyboard focus. Recommendation (not a failure): define an explicit :focus-visible style on the component (outline: 2px solid or box-shadow) so the indicator doesn't depend on UA defaults across browsers/themes. |
| # | Criterion | Status | Observation |
|---|---|---|---|
| 12.8 | Logical tab order | C | The three visible controls follow visual order (Play → Mute → Fullscreen). The seek and volume sliders are unreachable from the keyboard (see 7.2) — a separate gap rather than a tab-order inconsistency. |
| Priority | Fix |
|---|---|
| Critical | Make the seek bar keyboard-operable and expose it as a slider: either an <input type="range"> styled to match, or a div with role="slider" + aria-valuemin/max/now/text + tabindex="0" + key handlers (←/→ seek, Home/End jump). |
| Critical | Make the volume slider keyboard-operable with the same pattern and a label (aria-label="Volume"). |
| Low | Define an explicit :focus-visible style on the control buttons so the focus ring isn't dependent on Chrome's UA default (swap outline-style: none on :focus for a defined outline or box-shadow on :focus-visible). |
| High | Add aria-pressed to the Mute button and let its label reflect state ("Mute sound" / "Unmute sound") so screen readers announce state changes. |
| High | Add first-class captions / <track>-equivalent surface to the component API so authors can provide WebVTT captions for compositions with dialogue. |
| High | Provide a transcript slot (or documented pattern) for compositions with speech. |
| Medium | Give the player wrapper a landmark role and accessible name: role="region" + aria-label="Video player" (or aria-roledescription="video player"). |
| Medium | Respect prefers-reduced-motion for <Player autoPlay loop>: by default, do not autoplay/loop; document the override. |
| Medium | Add standard media keyboard shortcuts when the player region is focused: Space / K (play-pause), ←/→ (5 s seek), Shift+←/→ (fine seek), ↑/↓ (volume), M (mute toggle), F (fullscreen). |
| Low | Expose time via aria-valuetext on the seek slider, or make the time display a polite live region with explicit announcements on pause/seek. |
The Remotion Player component correctly labels its three visible control buttons but is not a fully accessible media player: seek and volume sliders carry no ARIA slider semantics and are not keyboard-operable, the focus indicator on all controls is suppressed, mute state is not programmatically exposed, the player region has no landmark/label, and the component offers no captions, transcript, audio-description, or reduced-motion surface.
Gaps are concentrated in 2.1.1 (Keyboard), 4.1.2 (Name, Role, Value), 2.4.7 (Focus Visible), 1.2.2 / 1.2.3 (Captions / descriptions), and 1.3.1 (Info and Relationships) at the media-player region level.
Reference: https://accessibilite.numerique.gouv.fr/methode/criteres-et-tests/