shadcn.io is an independent project built on shadcn/ui
Components
Free
Audio Player for Radix UI
Generated speech or any audio file, under a row of controls of your own. For React with Radix UI: play, seek, time, mute and volume.
A voice reply plays from one row of controls: play, seek back and forward, the time, a seek bar, the length, mute and volume.
Use it for text-to-speech output, voice messages and audio previews, where the browser's own player looks out of place in a chat. For a video, use Video Player. In React, the controls are Buttons and range inputs wired to a native audio element's events, so the browser still does the decoding and the buffering, and there's no media library underneath.
Installation
Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.
Every button has a name that says what it does now (Play or Pause, Mute or Unmute, Back 10 seconds), and the two ranges are native range inputs named Seek and Volume, the seek bar reading its time as "0:01 of 0:02". The control bar is a group named Audio controls. It stays left to right in right-to-left text, as media controls do: they follow the tape, not the reading direction. The shortcuts work while focus is anywhere inside the player; a focused button keeps Space and Enter, and a focused range keeps the arrows.
KeyAction
Space
Play or pause (anywhere but a button or a range).
K
Play or pause.
Arrow Left
Back 5 seconds.
Arrow Right
Forward 5 seconds.
J
Back 10 seconds.
L
Forward 10 seconds.
Arrow Up
Volume up.
Arrow Down
Volume down.
M
Mute or unmute.
Home
Go to the start.
End
Go to the end.
Demos
Compact
A voice message: a filled play button, the seek bar and the length, in a pill. Leave out the parts you don't need; each one reads the player from context, so any of them can go anywhere inside AudioPlayer.
Seek
Seek buttons on both sides of play, 2 seconds each way through seekOffset (10 by default), and the time counting down with remaining. This clip loads nothing until it's played (preload="none"), so its length shows as --:-- until then.
Transcript
The player and a Transcription share one time. onCurrentTimeChange reports the position as the audio plays, and a new currentTime seeks it, so a click on a sentence moves the player.
AI SDK
generateSpeech returns the audio as result.audio, with its bytes as base64 and its type as mediaType. Send those two to the page from your server route or action.
Pass them to AudioPlayerElement as data, and it plays them as a data: URL.
<AudioPlayerElementdata={speech} />
A data URL keeps the whole file in the page, which suits a reply of a few seconds. For a long reading, store the file and pass its URL as src.
Migrating from AI SDK Elements
If you added Audio Player from AI SDK Elements (opens in a new tab), your project has a components/ai-elements/audio-player.tsx built on media-chrome. Install this one, import from @/components/ui/audio-player instead, then delete the old file and remove media-chrome from your dependencies if nothing else uses it. Every part keeps its name, and src, data, seekOffset, remaining and showDuration work as before. What differs:
AudioPlayer is a div, not a MediaController: media-chrome's attributes and its --media-* CSS variables do nothing here. Style the parts with className.
AudioPlayerElement needs no slot="media", and it loads the audio's length on its own (preload="metadata").
AudioPlayer adds currentTime and onCurrentTimeChange, to follow and move the audio from outside.
FAQ
How do I play generated speech in Audio Player?
Pass the audio from the AI SDK's generateSpeech to AudioPlayerElement as data, an object with base64 and mediaType; it builds the data: URL. Any other file goes in src, as a URL. AudioPlayerElement takes every attribute an audio element takes, preload, loop and muted included.
Which controls does Audio Player have?
Play and pause, seek backward and forward (10 seconds unless seekOffset says otherwise), the time, a seek bar, the length, mute and volume. Use them all or only the ones you need, in any order inside AudioPlayerControlBar, as the Compact demo does with three of them.
Which keyboard shortcuts does Audio Player have in React?
With focus anywhere inside it: Space and K play and pause, the left and right arrows seek 5 seconds, J and L seek 10, the up and down arrows change the volume, M mutes, Home and End jump to the ends. A focused button keeps Space and Enter, and a focused range keeps its arrows.
How do I keep a transcript in step with Audio Player?
Keep the time in your own state: onCurrentTimeChange reports it as the audio plays, and setting currentTime seeks the audio. Pass the same state to Transcription as its currentTime, and its onSeek to the same setter, as the Transcript demo does.
How does Audio Player match my theme?
It's drawn with your theme's tokens: outline and ghost Buttons, the ranges in primary on input, the times in muted-foreground. Dark mode follows. AudioPlayer itself has no border or background, so it sits inside a message or a card; give it className for a frame of its own, as the demos do.
Why don't the controls mirror in right-to-left text?
Media controls follow the tape, not the reading direction: the seek bar fills left to right and back points left in any language, as on most players. AudioPlayerControlBar sets dir="ltr" for that reason. Pass dir="rtl" to it if you want the bar mirrored anyway.
How do I install Audio Player in React?
Run the command in the Installation section on this page. It adds the player's parts to your ui folder, with the Button they're built on and lucide for the icons. It has no media library underneath: the audio element does the playing.
The command uses the shadcn CLI.
API Reference
AudioPlayer
PropType
currentTime?number
The playback position in seconds. When it changes, the audio seeks to
it, so a transcript or a list of chapters can move the player.
onCurrentTimeChange?(seconds: number) → void
Called with the position in seconds as the audio plays and seeks.
Parameters
seconds – number
Returns
void
className?string
Merged with the part's classes.
AudioPlayerElement
<audio>data-slot="audio-player-element"
PropType
data?object
Generated speech to play, turned into a data: URL; or give src.
Type
{ base64: string; mediaType: string }
loop?boolean
Plays it again from the start when it ends.
muted?boolean
Starts it without sound.
preload?"" | "none" | "metadata" | "auto"
How much to load before it plays: none waits for a press, metadata
loads its length, auto may load it all.
Type
"" | "none" | "metadata" | "auto"
Default
"metadata"
src?string
The audio's URL; or give data.
className?string
Forwarded to the part.
AudioPlayerControlBar
<div>data-slot="audio-player-control-bar"
PropType
className?string
Merged with the part's classes.
AudioPlayerPlayButton
data-slot="audio-player-play-button"
PropType
clickEffect?boolean
Apply a click effect to the button.
Default
true
isLoading?boolean
Show a loading indicator.
Default
false
asChild?boolean
Merge props onto the child element instead of rendering a host.
className?string
Merged with the part's classes.
AudioPlayerSeekBackwardButton
data-slot="audio-player-seek-backward-button"
PropType
clickEffect?boolean
Apply a click effect to the button.
Default
true
isLoading?boolean
Show a loading indicator.
Default
false
seekOffset?number
How far a press seeks, in seconds.
Default
10
asChild?boolean
Merge props onto the child element instead of rendering a host.
className?string
Merged with the part's classes.
AudioPlayerSeekForwardButton
data-slot="audio-player-seek-forward-button"
PropType
clickEffect?boolean
Apply a click effect to the button.
Default
true
isLoading?boolean
Show a loading indicator.
Default
false
seekOffset?number
How far a press seeks, in seconds.
Default
10
asChild?boolean
Merge props onto the child element instead of rendering a host.
className?string
Merged with the part's classes.
AudioPlayerTimeDisplay
<span>data-slot="audio-player-time-display"
PropType
remaining?boolean
Counts down the time left instead of up.
Default
false
showDuration?boolean
Shows the duration after the time, 0:42 / 3:12.
Default
false
className?string
Merged with the part's classes.
AudioPlayerDurationDisplay
<span>data-slot="audio-player-duration-display"
PropType
className?string
Merged with the part's classes.
CSS variableType
--audio-player-range-fillstring
AudioPlayerTimeRange
<div>data-slot="audio-player-time-range"
PropType
className?string
Merged with the part's classes.
AudioPlayerMuteButton
data-slot="audio-player-mute-button"
PropType
clickEffect?boolean
Apply a click effect to the button.
Default
true
isLoading?boolean
Show a loading indicator.
Default
false
asChild?boolean
Merge props onto the child element instead of rendering a host.