AudioPlayer

A recording as a card: play, seek, skip, speed and download.

<AudioPlayer.Root
	src={recording.url}
	title="Kickoff call"
	description="Jane Doe · 30 s"
	durationSeconds={recording.seconds}
	downloadName="kickoff-call.mp3"
/>

The <audio> element stays hidden so every browser draws the same controls. durationSeconds is shown until the file's own metadata arrives — a webm or a stream reports Infinity until its end is known, so the figure you already have stands in.

Driving it from a transcript

ref exposes seek(seconds): it moves the playhead and plays from there, waiting for the metadata if the file has not loaded yet.

const player = useRef<AudioPlayerHandle>(null);
 
<AudioPlayer.Root ref={player} src={src} title="Kickoff call" />
<button onClick={() => player.current?.seek(17)}>Next steps</button>

Playback held higher up

useAudioPlayback owns one <audio> element's state apart from its controls. Hold it above your tabs and the recording keeps playing while the controls showing it mount and unmount — render AudioPlayer.Panel for the full card, or compose a smaller player from the parts.

const playback = useAudioPlayback({ src, durationSeconds: 12 });
 
<audio {...playback.element} />
<AudioPlayer.PlayToggle playback={playback} size="icon-sm" />
<AudioPlayer.SpeedToggle rate={playback.rate} onRateChange={playback.chooseRate} />

Labels

Every string comes through labels, English by default. rate formats a speed for its button, so a French player reads 1,5×:

<AudioPlayer.Root
	labels={{
		play: "Lire",
		pause: "Mettre en pause",
		rate: (rate) => `${String(rate).replace(".", ",")}×`,
	}}
	…
/>

API

AudioPlayer.Root

PropTypeDefault
srcstringrequiredThe file the download button offers; the panel itself plays nothing.
titleReactNoderequired
descriptionReactNode—
downloadNamestring—Offers the file as a download under this name; no button without it.
durationSecondsnumber | null—The length the caller already knows, shown until the file's own metadata arrives.
frameboolean—False inside a card of its own: the controls without their border and padding.
labelsPartial<AudioPlayerLabels>—
onRateChange((rate: number) => void)—
onTimeChange((seconds: number) => void)—
playbackRatesreadonly number[]—
ratenumber—A speed held by the caller (a user preference); the player keeps its own otherwise.
refRef<AudioPlayerHandle>—
skipSecondsnumber—
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus the DOM props of the element it renders. Source: audio-player/components/audio-player-root.tsx.

AudioPlayer.Panel

PropTypeDefault
playbackAudioPlaybackrequired
srcstringrequiredThe file the download button offers; the panel itself plays nothing.
titleReactNoderequired
descriptionReactNode—
downloadNamestring—Offers the file as a download under this name; no button without it.
framebooleantrueFalse inside a card of its own: the controls without their border and padding.
labelsPartial<AudioPlayerLabels>—
playbackRatesreadonly number[]DEFAULT_PLAYBACK_RATES
skipSecondsnumber15
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus every <div> prop. Source: audio-player/components/audio-player-panel.tsx.

AudioPlayer.PlayToggle

PropTypeDefault
playbackAudioPlaybackrequired
labelsPartial<Pick<AudioPlayerLabels, "pause" | "play">>—
loadingboolean—Show a leading spinner and mark the button aria-busy, disabling interaction while an async action is in flight (e.g. a form submit).
shape"default" | "pill"—pill forces a fully rounded button; default follows the theme's --radius, so it changes with the brand.
size"icon" | "icon-lg" | "icon-sm""icon-lg"
variant"brand" | "default" | "destructive" | "ghost" | "highlight" | "link" | "outline" | "primary" | "secondary"—Weight of the action. default is the one thing the page exists for, at most one per view; secondary an equal-weight alternative; outline for toolbars and anything that repeats in a row; ghost inside dense surfaces; destructive for what cannot be undone; link for an action that reads as navigation.
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.
classNamestring | ((state: ButtonState) => string | undefined)—CSS class applied to the element, or a function that returns a class based on the component's state.
focusableWhenDisabledboolean—Whether the button should be focusable when disabled.
nativeButtonboolean—Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (for example, <div>).
renderComponentRenderFn<HTMLProps, ButtonState> | ReactElement<unknown, string | JSXElementConstructor<any>>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.
styleCSSProperties | ((state: ButtonState) => CSSProperties | undefined)—Style applied to the element, or a function that returns a style object based on the component's state.

Plus the DOM props of the element it renders. Source: audio-player/components/audio-player-play-toggle.tsx.

AudioPlayer.SeekBar

PropTypeDefault
playbackAudioPlaybackrequired
labelsPartial<Pick<AudioPlayerLabels, "position" | "unavailable">>—
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.
classNamestring | ((state: SliderRootState) => string | undefined)—CSS class applied to the element, or a function that returns a class based on the component's state.
defaultValuenumber | readonly number[]—The uncontrolled value of the slider when it's initially rendered. To render a controlled slider, use the value prop instead.
disabledboolean—Whether the slider should ignore user interaction.
formstring—Identifies the form that owns the slider inputs. Useful when the slider is rendered outside the form.
formatNumberFormatOptions—Options to format the value.
largeStepnumber—The granularity with which the slider can step through values when using Page Up/Page Down or Shift + Arrow Up/Arrow Down.
localeLocalesArgument—The locale used by Intl.NumberFormat when formatting the value. Defaults to the user's runtime locale.
minStepsBetweenValuesnumber—The minimum steps between values in a range slider.
namestring—Identifies the field when a form is submitted.
onValueCommitted((value: number | readonly number[], eventDetails: SliderRootCommitEventDetails) => void)—Callback function that is fired when a value change is committed. Does not fire if the value did not change, or if the change was canceled. Warning: This is a generic event, not a change event. The eventDetails.reason indicates what triggered the commit:
orientationOrientation—The component orientation.
renderComponentRenderFn<HTMLProps, SliderRootState> | ReactElement<unknown, string | JSXElementConstructor<any>>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.
styleCSSProperties | ((state: SliderRootState) => CSSProperties | undefined)—Style applied to the element, or a function that returns a style object based on the component's state.
thumbAlignment"center" | "edge" | "edge-client-only"—How the thumb(s) are aligned relative to Slider.Control when the value is at min or max:
thumbCollisionBehavior"none" | "push" | "swap"—Controls how thumbs behave when they collide during pointer interactions.

Plus the DOM props of the element it renders. Source: audio-player/components/audio-player-seek-bar.tsx.

AudioPlayer.SkipButtons

PropTypeDefault
playbackAudioPlaybackrequired
labelsPartial<Pick<AudioPlayerLabels, "back" | "forward">>—
secondsnumber15How far one press moves.
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

Plus every <div> prop. Source: audio-player/components/audio-player-skip-buttons.tsx.

AudioPlayer.SpeedToggle

PropTypeDefault
onRateChange(rate: number) => voidrequired
ratenumberrequired
labelsPartial<Pick<AudioPlayerLabels, "rate" | "speed">>—
loadingboolean—Show a leading spinner and mark the button aria-busy, disabling interaction while an async action is in flight (e.g. a form submit).
ratesreadonly number[]DEFAULT_PLAYBACK_RATES
shape"default" | "pill"—pill forces a fully rounded button; default follows the theme's --radius, so it changes with the brand.
size"default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs"—Height and horizontal padding. The icon-* set is square, for a button whose entire content is one icon — give those an aria-label.
variant"brand" | "default" | "destructive" | "ghost" | "highlight" | "link" | "outline" | "primary" | "secondary"—Weight of the action. default is the one thing the page exists for, at most one per view; secondary an equal-weight alternative; outline for toolbars and anything that repeats in a row; ghost inside dense surfaces; destructive for what cannot be undone; link for an action that reads as navigation.
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.
classNamestring | ((state: ButtonState) => string | undefined)—CSS class applied to the element, or a function that returns a class based on the component's state.
focusableWhenDisabledboolean—Whether the button should be focusable when disabled.
nativeButtonboolean—Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (for example, <div>).
renderComponentRenderFn<HTMLProps, ButtonState> | ReactElement<unknown, string | JSXElementConstructor<any>>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.
styleCSSProperties | ((state: ButtonState) => CSSProperties | undefined)—Style applied to the element, or a function that returns a style object based on the component's state.

Plus the DOM props of the element it renders. Source: audio-player/components/audio-player-speed-toggle.tsx.

AudioPlayer.DownloadButton

PropTypeDefault
namestringrequiredThe file name the browser saves under.
srcstringrequired
labelsPartial<Pick<AudioPlayerLabels, "download">>—
loadingboolean—Show a leading spinner and mark the button aria-busy, disabling interaction while an async action is in flight (e.g. a form submit).
shape"default" | "pill"—pill forces a fully rounded button; default follows the theme's --radius, so it changes with the brand.
size"default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs"—Height and horizontal padding. The icon-* set is square, for a button whose entire content is one icon — give those an aria-label.
variant"brand" | "default" | "destructive" | "ghost" | "highlight" | "link" | "outline" | "primary" | "secondary"—Weight of the action. default is the one thing the page exists for, at most one per view; secondary an equal-weight alternative; outline for toolbars and anything that repeats in a row; ghost inside dense surfaces; destructive for what cannot be undone; link for an action that reads as navigation.
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.
classNamestring | ((state: ButtonState) => string | undefined)—CSS class applied to the element, or a function that returns a class based on the component's state.
focusableWhenDisabledboolean—Whether the button should be focusable when disabled.
styleCSSProperties | ((state: ButtonState) => CSSProperties | undefined)—Style applied to the element, or a function that returns a style object based on the component's state.

Plus the DOM props of the element it renders. Source: audio-player/components/audio-player-download-button.tsx.