Audio Player
A custom audio transport with play/pause, skip, seek, a tappable speed control, and keyboard shortcuts — single-line on a wide player, two lines on mobile.
- Status
- Since
0.5.0- Accessibility pattern
- native media controls
Last updated
Install
Add Audio Player from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/audio-playerThe same command installs the registry items it composes: @vegastack/media-player-controls.
Usage
import { AudioPlayer } from "@/components/ui/audio-player";
<AudioPlayer src="/media/demo.mp3" label="Product demo audio" />;Examples
Basic
A single-line transport rendered statically on a card. Left to right: play/pause, rewind and
forward (±15s), the combined elapsed / duration readout, a flexible seek bar, and a tappable
speed control. The chrome uses the subdued secondary emphasis rather than the loud brand accent.
The docs preview uses a 90-second local audio fixture, so it can play without network access.
Mobile layout
Below the @sm container width the transport reflows to two lines while staying compact. The top
line carries the seek bar with elapsed and duration pinned to either edge in a smaller font;
the bottom line centres play/pause with rewind and forward flanking it, a transcript control on the
leading edge, and the tappable speed on the trailing edge. The transcript control renders only when
onTranscriptClick is supplied — wire it to open your app's transcript. The example is width-limited
so the mobile layout is visible on a desktop screen.
Waveform
Set variant="waveform" to render the decoded audio as the seek bar. The waveform is sampled
from the real audio via the Web Audio API — played bars fill as playback advances, and the bars
seek by click, drag, or keyboard just like the slider. Until decoding finishes (or if it fails),
flat placeholder bars render and the seek stays operable.
With copy
Use title and description when the player needs a visible track name or short context.
Playback rates
Pass playbackRates and defaultPlaybackRate to control the options the tappable speed
control cycles through. Each tap advances to the next rate in order and wraps past the end.
Playground
Every AudioPlayer prop, auto-generated from its TypeScript types.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
src* | string | — | Audio source URL. |
className | string | — | Classes applied to the outer player container. |
defaultPlaybackRate | number | 1 | Initial playback rate applied when the audio element mounts. |
description | React.ReactNode | — | Optional visible description shown below the title. |
formatTime | ((seconds: number) => string) | mm:ss / h:mm:ss | Format elapsed and duration labels. |
label | string | 'Audio' | Accessible label used by the audio element and custom controls. |
mediaRef | React.Ref<HTMLAudioElement> | — | Ref for the hidden native <audio> media engine. |
onPlaybackRateChange | ((playbackRate: number) => void) | — | Called whenever the playback rate changes. |
onPlayStateChange | ((playing: boolean) => void) | — | Called whenever playback starts or pauses. |
onTimeChange | ((currentTime: number, duration: number) => void) | — | Called whenever the current playback time changes. |
onTranscriptClick | (() => void) | — | Called when the transcript control is pressed on a narrow, mobile-width player — wire it to open the consumer app's transcript. When omitted, the transcript control is not rendered. |
playbackRates | readonly number[] | [1, 1.25, 1.5, 2, 0.5] | Playback rates cycled by the tappable speed control, in tap order. Each tap advances to the next entry and wraps past the end. |
ref | React.Ref<HTMLDivElement> | — | Ref for the outer player container. |
skipSeconds | number | 15 | Seconds moved by the visible rewind/forward buttons and the keyboard skip. |
title | React.ReactNode | — | Optional visible title shown above the transport. |
variant | "default" | "waveform" | 'default' | Seek presentation. waveform renders a decoded-audio waveform in place of
the seek slider; the slider's keyboard and pointer semantics are preserved
beneath the bars. |
Data attributes and CSS variables on AudioPlayer
| Attribute | Values |
|---|---|
data-slot | "audio-player" | "audio-player-header" |
data-variant | mirrors a prop or state value |
Accessibility
| Key | Action |
|---|---|
| Tab | Move through play/pause, rewind, forward, seek, and speed |
| Enter / Space | Activate the focused button |
| K | Play or pause |
| J | Rewind 15 seconds |
| L | Forward 15 seconds |
| M | Toggle mute |
| ← / ↓ | Decrease the seek position by one second while the slider is focused |
| → / ↑ | Increase the seek position by one second while the slider is focused |
| Home | Seek to the beginning |
| End | Seek to the end |
- Pass a descriptive
label; it names the media element and prefixes every custom control. - The seek rail composes
Slider, so keyboard and screen-reader slider semantics stay intact. - Icon-only transport buttons are composed with
IconButton, which requires accessible names. - Tooltips expose the primary keyboard shortcuts for pointer and keyboard users.
- Playback speed is a single tappable button that cycles the rates; its accessible name always announces the current speed.
- Rewind and forward are visible buttons on both layouts (inline on a wide player; flanking play/pause on the second line of a narrow, mobile-width player) and keyboard shortcuts anywhere inside the player (J / L).
- The transcript control appears only on the narrow layout and only when
onTranscriptClickis supplied; it carries an accessible name derived fromlabeland opens whatever the consumer wires. - Mute and a volume rail are present in both layouts. The rail is a vertical
Sliderthat opens from the mute button on hover or focus; mute also stays on the M key. (Before the 2026-09-07 audit, audio had no volume control at all and mute was keyboard-only.) - The seek thumb is hidden at rest only where a pointer can hover. On a touch device it stays visible — without it there is no scrub affordance at all.
- The native
<audio>element is used as the media engine and does not expose browser-native controls; VegaStack renders the consistent control surface.
| Contract | States tested |
|---|---|
| Behaviour | default, playing, paused, muted, volume, seeking, keyboard-shortcuts, playback-rate, transcript |
| Accessibility | focus-visible, keyboard-operable, labeled, semantic-html |
| Visual | default, with-copy, waveform |