Audio Player React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React audio player with a waveform you can see and scrub, as a podcast card, a chat voice message or a thin call-log bar.
Press play: the icon turns into pause on a quick spring, the played bars fill in the tone as the recording runs and the time counts up. Tap or click anywhere on the waveform to jump there; drag along it to scrub (the bars and the time follow the finger, the audio seeks on release, and every 120ms with a mouse so you hear where you are). With a mouse, hovering shows a cursor line with the time above it. The card has skip back / forward buttons (15 seconds), a speed menu (a bottom sheet on phones), a volume button with a slider (hidden on touch screens, where the hardware keys do it) and an optional download. The compact voice-message player cycles 1×, 1,5×, 2× on each tap of its chip and shows the length until it starts, then the position. Starting one player pauses the others. While playing, the phone's lock screen and headphone buttons control it. Keyboard: Tab reaches the play button, the waveform and each control; on the waveform Left / Right (or Up / Down) seek 5 s, PageUp / PageDown 30 s, Home / End jump to the start or end, Space, k or Enter play and pause; anywhere in the player Space / k play, j / l seek 10 s, m mutes, Up / Down change the volume, 0 to 9 jump to 0% to 90%, < and > change the speed. A missing file shows a message with a retry. The file also exports Waveform (the bars alone for custom layouts: peaks, progress as a number or a motion value, barCount / barWidth / barGap / barRadius / height, tone / color, onSeek(fraction) makes it a slider with click, drag and arrow keys, onScrub(fraction | null), formatBubble(fraction) for the hover bubble, peaksKey to fade in new bars), computePeaks(buffer, count) (max absolute sample per bucket, normalized 0 to 1), decodePeaks(src, count, { signal }) (fetch, decodeAudioData and computePeaks, cached per src and count) and placeholderPeaks(seed, count) (stable speech-like bars, the same on the server and the client).
Audio Player is a Pro React component in the React media components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add audio-player, together with Button, Dropdown Menu, Popover, Slider, Spinner and 8 more and the npm packages motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add audio-playerAudio Player is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import AudioPlayer from @/components/ui/audio-player.
import { AudioPlayer } from "@/components/ui/audio-player"
export function Episode() {
return (
<AudioPlayer
src="/demo/podcast.mp3"
title="Sales tax for small businesses"
subtitle="Summit Podcast · Ep. 12 · Sep 24, 2026"
cover="/demo/mountains.jpg"
downloadable
labels={{ download: "Save episode" }}
/>
)
}
export function VoiceNote() {
return <AudioPlayer variant="compact" src="/demo/voice-message.mp3" labels={{ play: "Listen", pause: "Stop" }} />
}Props
src*stringThe audio file url. Decoding peaks needs the file on the same origin or served with CORS.
titledefault""ReactNodeThe card's title, and the lock-screen title while playing (when it is a string).
subtitledefault""ReactNodeA muted line under the title (show, author, date); the lock-screen artist while playing.
coverstringSquare artwork url for the card. It sits at 94% while paused and grows to full size while playing. Also the lock-screen artwork.
leadingReactNodeCompact only: an avatar or a mic icon before the play button, the chat look.
variantdefault"card""card" | "compact" | "minimal"card: cover, title, waveform and a controls row on a raised panel. compact: the voice message (play, bars, time, speed chip) for chat bubbles. minimal: play, a thin scrubbable bar and the time for lists and call logs.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Colors the played bars and the play button. neutral is black in light mode and white in dark mode.
colordefault""stringAny CSS color for the played bars and the play button; replaces the tone. The play icon turns black or white by lightness.
sizedefault"md""sm" | "md"Button sizes: md has a 40px play button in the card (32px compact and minimal), sm 32px (28px). Every button keeps a 44px touch target.
radiusdefault"md""none" | "sm" | "md" | "lg"Corner radius of the card surface (12 / 16 / 24px) and its cover.
barCountdefault0numberHow many bars. 0 fits as many as the width allows (barWidth + barGap each, remeasured on resize); a fixed count spreads over the width.
barWidthdefault3numberWidth of each bar in px.
barGapdefault2numberSpace between bars in px.
barRadiusdefault2numberCorner radius of each bar in px.
waveformHeightdefault0numberHeight of the waveform in px. 0 is automatic: 40px in the card, 28px in the compact player.
peaksnumber[]Precomputed bar heights from 0 to 1, any length (resampled to the bar count, max per bucket). Skips decoding.
decodePeaksdefaulttruebooleanWithout peaks, fetch and decode the file with Web Audio once the player is on screen (one decode per file, cached), fading from placeholder bars to the real waveform. A CORS-blocked file stays on the placeholder bars. The file also exports a decodePeaks(src, count, { signal }) function that returns the same peaks as a Promise<number[]>, for precomputing them yourself.
skipSecondsdefault15numberSeconds for the card's skip back / forward buttons and the lock-screen skip. 0 hides the buttons.
ratesnumber[]Playback speeds. Compact cycles through them on tap (default [1, 1.5, 2]); the card opens a menu (default MEDIA_RATES, 0.5 to 2). One entry hides the speed control.
showVolumedefault"auto""auto" | "always" | "never"The card's volume button and slider. auto hides it on touch screens, where people use the hardware keys.
downloadabledefaultfalsebooleanA download button (card and minimal) that saves the file through file-export's downloadUrl and announces it.
fileNamedefault""stringThe downloaded file's name, sanitized, keeping the source's extension. Empty keeps the name from the url.
exclusivedefaulttruebooleanStarting this player pauses every other exclusive player on the page, so two voice notes never talk over each other.
mediaSessiondefaulttruebooleanWhile playing, sets the lock-screen metadata (title, subtitle, cover) and handles play, pause, seek back / forward and seek-to from the lock screen and headphones.
preloaddefault"metadata""none" | "metadata" | "auto"The audio element's preload. metadata loads the duration only; none shows --:-- until the first play.
loopdefaultfalsebooleanStarts over at the end.
localedefault""stringFormats the speed labels (1.5× in en-US, 1,5× in de-DE). Empty uses UIProvider, then the page language.
onPlay() => voidPlayback started (passed to useMediaControls).
onPause() => voidPlayback paused.
onEnded() => voidThe recording reached its end (the play icon turns into replay).
onError(error: MediaError | null) => voidThe file could not be loaded or played; the player shows the error row with a retry.
onTimeUpdate(seconds: number) => voidWhole seconds while playing and after seeks: resume positions, listening analytics.
setApi(api: AudioPlayerApi) => voidThe media controls without the ref: playing, second, duration, progress (a motion value), play(), pause(), seek(seconds), seekBy, setRate... Called after mount and again when the state changes (at most once a second while playing), so it can go straight into state.
labelsPartial<AudioPlayerLabels>Every string: play "Play", pause "Pause", replay "Replay", loading "Loading", seek "Seek", skipBack(s) "Back {s} seconds", skipForward(s), speed "Speed", cancel "Cancel" (the speed sheet on phones), volume "Volume", mute "Mute", unmute "Unmute", download "Download", downloaded(name), error "The recording could not be played", retry "Try again", live "Live", position(current, total) "{current} of {total}". DEFAULT_AUDIO_PLAYER_LABELS holds the English set.
classNamesPartial<Record<AudioPlayerSlot, string>>Classes for the parts: audio, header, cover, meta, title, subtitle, leading, play, waveform, bar, time, controls, skipBack, skipForward, rate, volume, download, error, live.
Parts
Waveform
componentThe bar waveform on its own: draws peaks with a played share, and with onSeek becomes a slider you click, tap, drag or move with the arrow keys. Use it for a custom player layout.
peaks*readonly number[]Bar heights from 0 to 1, any length; resampled to the bar count.
progressdefault0number | MotionValue<number>The played share from 0 to 1; a motion value moves without re-rendering.
barCountdefault0number0 fits as many bars as the width allows.
barWidthdefault3numberBar width in px.
barGapdefault2numberGap between bars in px.
barRadiusdefault2numberBar corner radius in px.
heightdefault40numberHeight in px.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Color of the played bars.
colorstringAny CSS color for the played bars; replaces the tone.
onSeek(fraction: number) => voidMakes the bars a slider: a click or tap seeks, a drag scrubs.
onScrub(fraction: number | null) => voidThe fraction under the pointer while dragging, then null.
seekIntervaldefault120numberms between seeks while dragging with a mouse; 0 seeks only on release.
formatBubble(fraction: number) => stringThe text of the bubble above the cursor, such as 1:12.
keyStepdefault0.05numberArrow key step as a fraction; PageUp and PageDown move four steps.
disableddefaultfalsebooleanTurns off seeking.
peaksKeydefault"peaks"string | numberChange it to fade in new bars, from placeholder to decoded peaks.
computePeaks
functionTurns a decoded AudioBuffer into bar heights: the loudest sample per bucket over the first two channels, normalized so the loudest bar is 1. Signature: (buffer: AudioBuffer, count: number) => number[].
placeholderPeaks
functionStable pseudo-random bars from any string (usually the src), shaped like speech with short pauses, shown until the real peaks arrive. Signature: (seed: string, count: number) => number[].
DEFAULT_AUDIO_PLAYER_LABELS
constantThe English strings of the buttons, the slider and the error state. Spread it to translate a few. Type: AudioPlayerLabels.
AudioPlayerApi
typeWhat setApi receives: the useMediaControls result without its ref (playing, duration, second, progress, play(), pause(), toggle(), seek(seconds), seekBy(delta), setRate and the volume controls).
Examples
Voice messages in a chat
Compact players with an avatar. Exclusive: starting one pauses the other. The chip cycles 1×, 1.5×, 2×.
Decoded peaks
No peaks prop: the file is decoded with Web Audio when the card scrolls into view.
Call log
The minimal variant in CRM rows: play, a thin scrubbable bar, the time and a download.
Custom color, dense bars
color="#7C3AED" with barWidth 2 and barGap 1, a taller waveform and a download button.
Error and retry
A missing file shows the message and a retry that reloads the element.
Transcript with setApi
The controls object drives a transcript: the current sentence highlights, a tap seeks there.
Sizes and tones
md and sm buttons for the compact and minimal players, with a tone on the played part.
Dependencies
Files
- components/ui/audio-player.tsx
FAQ
Is the React Audio Player component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Audio Player?
Run npx wingo-ui@latest add audio-player. It also adds the Wingo UI items it builds on (Button, Dropdown Menu, Popover, Slider, Spinner and 8 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/audio-player.
How does Audio Player behave on phones?
The card has skip back / forward buttons (15 seconds), a speed menu (a bottom sheet on phones), a volume button with a slider (hidden on touch screens, where the hardware keys do it) and an optional download.
See also
Used in
More React media components
Articles using this component
- React Audio Player with Waveform: Scrub, Seek, Peaks
Component guides9 min read
- React Speech to Text Input: Mic, Waveform, Fallback
Building UI with AI agents9 min read
