Audio Player

A composable audio player. One component, one install, fully owned by you.

Everything you need to build an audio player is in a single file: the engine, the UI controls, the queue, tracks, and playback speed. No separate installs, no cross-file wiring. Just one component you copy, paste, and own.

Installation

Philosophy

Composable by default. AudioPlayer is just a container. Every control (seek bar, volume, queue, playback speed) is an independent component you drop in where you need it. No black-box layout, no hidden markup.

State lives in the store, not in components. All playback state (current track, progress, queue, shuffle, repeat) is held in a Zustand store. Components read and write that store directly. No prop drilling, no context threading.

The engine is separate from the UI. AudioProvider wires up the HTML audio element to the store: event listeners, retries, preloading, state restoration. UI components just read state and call store actions.

Setup

Pass tracks directly to AudioPlayer for a self-contained widget:

import { AudioPlayer, AudioPlayerControlBar, AudioPlayerPlay } from "@/components/audio/player";
 
<AudioPlayer tracks={myTracks}>
  <AudioPlayerControlBar>
    <AudioPlayerPlay />
  </AudioPlayerControlBar>
</AudioPlayer>

For shared state across multiple players or app-wide persistence, mount AudioProvider once at the root:

// app/layout.tsx
import { AudioProvider } from "@/components/audio/player";
 
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AudioProvider tracks={initialTracks}>{children}</AudioProvider>
      </body>
    </html>
  );
}

tracks vs AudioProvider: Use tracks on AudioPlayer for self-contained widgets. Use AudioProvider at the root when multiple components share the same playback session.

Player

Compact

With queue

Variants

default

ghost

Sizes

sm

default

AudioPlayer

Container for all player controls.

PropTypeDefaultDescription
tracksTrack[]-When provided, wraps children with AudioProvider automatically.
variant"default" | "ghost""default"Visual style. ghost is transparent with a hover background.
size"sm" | "default""default"Controls padding and border radius.
classNamestring-Additional CSS classes.

Inherits all other props from HTMLDivElement. The underlying CVA definition is exported as audioPlayerVariants for extension.

AudioProvider

Connects the HTML audio element to the Zustand store. Handles event listeners, retries, preloading, and state restoration.

PropTypeDefaultDescription
tracksTrack[][]Initial tracks to populate the queue.
childrenReactNode-Components that share this audio session.

AudioPlayerButton

Shared button primitive used by all player controls. Wraps a Button with an optional tooltip.

PropTypeDefaultDescription
tooltipLabelstring-When provided, wraps the button in a tooltip.

Inherits all other props from Button.

AudioPlayerControlBar

Groups controls in a single row or stacked column layout.

PropTypeDefaultDescription
variant"compact" | "stacked""compact"Controls the layout.

AudioPlayerControlGroup

Flexible row wrapper for arranging control clusters. Use className to adjust alignment.

AudioPlayerPlay

Play/pause button. Shows a spinner during loading/buffering. Spacebar shortcut is enabled automatically.

Inherits all props from AudioPlayerButton.

AudioPlayerSkipBack / AudioPlayerSkipForward

Navigate between tracks.

Inherits all props from AudioPlayerButton.

AudioPlayerRewind / AudioPlayerFastForward

Seek backward or forward by 10 seconds. Disabled for live streams.

Inherits all props from AudioPlayerButton.

AudioPlayerSeekBar

Seek timeline showing playback and buffered progress. Locked to 100% for live streams.

Inherits all props from Transport (except value, onSeek, bufferedValue).

AudioPlayerTimeDisplay

Displays current time or remaining time. Shows a live indicator for streams.

PropTypeDefaultDescription
remainingbooleanfalseShow remaining time instead of elapsed time.

AudioPlayerVolume

Dropdown with a horizontal fader to control volume and mute.

Inherits all props from Fader (except value, onValueChange, min, max, orientation, size).

Tracks

AudioTrackList renders a list of tracks. Omit tracks to read from the global queue; pass tracks for controlled mode.

Single track

Default

No tracks found
Try adding some tracks

Grid

No tracks found
Try adding some tracks

Sortable

No tracks found
Try adding some tracks

Sortable Grid

No tracks found
Try adding some tracks

AudioTrack

Renders a single track row.

PropTypeDefaultDescription
trackIdstring | number-Look up a track from the queue by id (store mode).
trackTrack-Render a provided track object (controlled mode).
indexnumber-Display index (one-based when shown).
onClick() => void-Called when the row is clicked.
onRemove(trackId: string) => void-Called when the remove action is triggered.
media"cover" | "drag-handle" | "drag-handle-with-cover" | "index""cover"Left-side media variant.
actions"none" | "play-pause" | "remove" | "play-pause-with-remove""play-pause"Right-side action variant.

Mode constraint: Use either trackId or track, not both. trackId requires an AudioProvider in the tree.

AudioTrackList

PropTypeDefaultDescription
tracksTrack[]-Controlled list. Omit to read from the global queue.
onTrackSelect(index: number, track?: Track) => void-Called when a track is selected.
onTrackRemove(trackId: string) => void-Called when a track is removed. Enables remove actions automatically.
mode"static" | "sortable""static"Enable drag-and-drop reordering.
media"cover" | "index""cover"Media style. In sortable mode a drag handle is added automatically.
actions"none" | "play-pause" | "remove" | "play-pause-with-remove"AutoAction variant per row. Auto: "play-pause-with-remove" when onTrackRemove is set.
variant"default" | "grid""default"Stacked list or responsive grid.
filterQuerystring-Text filter (matches title or artist).
filterFn(track: Track) => boolean-Custom filter. Overrides filterQuery when both are set.
emptyLabelstring"No tracks found"Empty state label.
emptyDescriptionstring"Try adding some tracks"Empty state description.

Sortable constraint: Drag-and-drop reordering only updates the store when the list is unfiltered and in store mode.

Queue

All queue controls work together or independently.

Simple

All controls

Shuffle and repeat

Preferences

AudioQueue

A dialog with the full queue: search, selection, remove, and drag-and-drop reordering.

PropTypeDefaultDescription
onTrackSelect(index: number) => void-Called when a track is selected from the queue.
searchPlaceholderstring"Search for a track..."Search input placeholder.
emptyLabelstring"No tracks found"Empty state label.
emptyDescriptionstring"Try searching for a different track"Empty state description.

Search and reorder: Drag-and-drop is disabled while a search filter is active.

AudioQueueShuffle

Toggle that enables/disables shuffle. Persisted to localStorage.

Inherits all props from Toggle (except onPressedChange).

AudioQueueRepeatMode

Toggle that cycles through repeat modes: noneallone. Persisted to localStorage.

Inherits all props from Toggle.

AudioQueuePreferences

Dropdown combining repeat mode and insert mode (first, last, after current) in one compact menu.

Inherits all props from AudioPlayerButton.

Playback Speed

AudioPlaybackSpeed

Dropdown to change playback speed. Disabled automatically for live streams.

PropTypeDefaultDescription
speedsreadonly { value: number; label: string }[]PLAYBACK_SPEEDSSpeed options. Default: 0.5×, 0.75×, 1×, 1.25×, 1.5×, 2×.
sizeButtonSize-"icon" hides the gauge icon, shows only the label.
variantButtonVariant"outline"Button variant.

Notes

Live streams: Seeking, rewind, fast-forward, and playback speed are disabled automatically. The seek bar locks to 100%.

Spacebar shortcut: AudioPlayerPlay registers a global keydown listener for Space when the document body is focused.

Changelog

2026-04-17 data-slot coverage and AudioPlayer variants

  • Added: AudioPlayer now accepts variant (default | ghost | outline) and size (sm | default | lg) props via CVA
  • Added: audioPlayerVariants exported for extension
  • Added: data-slot attributes on all components for CSS targeting (audio-player, audio-player-button, audio-seek-bar, audio-track, audio-track-list, audio-queue-trigger)
  • Added: data-current="true" on AudioTrack when it is the active track
  • Added: data-variant on AudioPlayer and AudioPlayerControlBar

2026-04-15 Unified into single file

  • Changed: provider.tsx, queue.tsx, track.tsx, playback-speed.tsx merged into player.tsx
  • Changed: Provider logic extracted into useAudioProvider hook
  • Added: AudioPlayer now accepts a tracks prop, self-provisions the audio engine
  • Removed: AudioQueueButton, AudioPlaybackSpeedButton, consolidated into AudioPlayerButton

2026-04-05 Player API alignment and transport migration

  • Changed: AudioPlayerSeekBar now uses Transport instead of Slider
  • Changed: AudioPlayerVolume now uses horizontal Fader
  • Improved: AudioPlayerButton applies aria-label from tooltipLabel by default

2025-12-24 useAudio integration

  • Changed: Provider now uses useAudio() hook for the audio singleton
  • Improved: Event listeners managed via AbortController

Last updated 4/17/2026