WaveformPlayer renders audio as a canvas. Canvas pixels are not accessible on their own, so the component-vs-app contract leans heavier on the app: the product must supply text alternatives (transcripts, time readouts, region lists) and keyboard paths to reach playback and region operations. WaveformPlayer handles the playback plumbing, region rendering, loading and error state strings, and the imperative ref surface that lets external UI drive the canvas.
What the component handles
- Playback lifecycle via WaveSurfer.js, wrapped in a stable ref (
play, pause, seekTo, getCurrentTime, addRegion, removeRegion, zoomTo).
- Region overlay rendering from the
WaveformRegion[] array passed in, with stable id values for external mapping.
- Loading and error states rendered as accessible text, not just spinners or empty canvases.
- Respect for
prefers-reduced-motion on zoom and region-edit animations.
- A text alternative when the canvas alone would be the only way to understand the clip: transcript, region list, and/or duration text.
- Keyboard paths for every task: if the canvas drives seek via click, expose an equivalent via
Slider or dedicated buttons that call ref.seekTo.
- Accessible names and state for region controls (add, delete, select) when your product UI exposes them.
- Labels for region entries that describe purpose, not raw timestamps ("Caller PII", "Dispatcher intro").
- A fallback for users who can't use the canvas: transcript download, a non-canvas audio fallback, or a table of regions with time columns.
- Tab / Shift+Tab: move between external playback controls, region list, and any zoom or speed controls you compose around the canvas.
- Enter or Space: activate the focused external control.
- Arrow keys (when a seek slider has focus): seek by the slider step.
- The canvas itself is not a focus target; keyboard users rely on the surrounding controls your product exposes.
- The canvas element is decorative from AT's perspective; don't label it with
role="img" without a meaningful alternative text path.
- Wrap the canvas in a labelled region (
<section aria-labelledby={titleId}>) so screen reader users hear what clip is loaded.
- Region list items should expose start and end times as readable text ("Caller PII, 0:12 to 0:34") and carry button semantics when they're clickable.
- When regions represent destructive annotations (redactions), follow destructive-action conventions on any delete controls—confirm before removing.
Screen reader announcements
- Loading complete: announce once via a polite live region ("Waveform loaded"); don't repeat on seek.
- Error: surface as inline text and announce via the same live region ("Waveform unavailable, retry").
- Region add or remove: if the user is editing regions, announce changes politely ("Added region: Caller PII, 0:12 to 0:34").
- Playback state: relies on the external control labels ("Pause clip", "Play clip"), not canvas announcements.
- Zoom animates briefly; respect
prefers-reduced-motion: reduce and snap instead of sliding when set. Region boundary drags should fade feedback rather than pulse.
Touch targets and responsive behavior
- Region handles are narrow by default. When your product exposes region editing on touch, provide larger hit targets around each region boundary or require explicit tap-to-edit mode.
- The canvas needs horizontal room; below a reasonable desktop width (roughly 600 px), fall back to the compact
AudioPlayer with a separate transcript surface rather than compressing the waveform into an unreadable strip.
- Canvas content is not perceivable for screen readers or low-vision users alone; the transcript or region list is the real accessibility story here.
- WaveSurfer decodes on load; long clips may hold the main thread briefly. Keep surrounding UI usable (focus escapes the region, loading text stays fresh).
- Region labels are the only signal of region purpose for many users; don't rely on region color.
- External control labels (from
ref-driven UIs) are the accessible names for playback; if you hide the internal cluster, make sure the external UI is fully labelled.