Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

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
stable
Since
0.5.0
Accessibility pattern
native media controls

Last updated

0:00
0:00

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-player

The 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.

0:00
0:00

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.

0:00
0:00

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.

Podcast episode
The waveform variant renders the decoded audio as the seek bar.
0:00
0:00

With copy

Use title and description when the player needs a visible track name or short context.

Launch briefing
A compact audio transport with shared media controls.
0:00
0:00

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.

0:00
0:00

Playground

Every AudioPlayer prop, auto-generated from its TypeScript types.

API Reference

PropTypeDefaultDescription
src*stringAudio source URL.
classNamestringClasses applied to the outer player container.
defaultPlaybackRatenumber1Initial playback rate applied when the audio element mounts.
descriptionReact.ReactNodeOptional visible description shown below the title.
formatTime((seconds: number) => string)mm:ss / h:mm:ssFormat elapsed and duration labels.
labelstring'Audio'Accessible label used by the audio element and custom controls.
mediaRefReact.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.
playbackRatesreadonly 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.
refReact.Ref<HTMLDivElement>Ref for the outer player container.
skipSecondsnumber15Seconds moved by the visible rewind/forward buttons and the keyboard skip.
titleReact.ReactNodeOptional 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

AttributeValues
data-slot"audio-player" | "audio-player-header"
data-variantmirrors a prop or state value

Accessibility

KeyAction
TabMove through play/pause, rewind, forward, seek, and speed
Enter / SpaceActivate the focused button
KPlay or pause
JRewind 15 seconds
LForward 15 seconds
MToggle 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
HomeSeek to the beginning
EndSeek 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 onTranscriptClick is supplied; it carries an accessible name derived from label and opens whatever the consumer wires.
  • Mute and a volume rail are present in both layouts. The rail is a vertical Slider that 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.
ContractStates tested
Behaviourdefault, playing, paused, muted, volume, seeking, keyboard-shortcuts, playback-rate, transcript
Accessibilityfocus-visible, keyboard-operable, labeled, semantic-html
Visualdefault, with-copy, waveform

Do / Don't

Do
Use AudioPlayer for a single audio asset that needs a compact, branded transport.
Don't
Use AudioPlayer for playlists, queue management, waveform editing, or transcript navigation.

On this page