Audio Recorder
Record a voice note or an audio clip, listen back on its waveform, then save it or discard it.
import { AudioRecorder, AudioRecordingInfo } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use an audio recorder wherever people leave a voice note or a short clip: a message composer, a support form, a spoken answer in a survey. The first press of the record button asks for the microphone. While it records, the sound scrolls past as bars, a timer counts up, and pause and stop sit beside it. A stopped take opens on a waveform scrubber with play, its length, discard and Save, which hands you the take. The recorder keeps one size through every state: the controls change, the box doesn't.
import { AudioRecorder } from '@youj/react'
<AudioRecorder
maxDuration={60}
onRecordingComplete={(blob, { duration, mimeType }) => upload(blob, { duration, mimeType })}
/>
// A recording you already have opens on playback.
<AudioRecorder value={savedNote} />It records with MediaRecorder, in mimeType when the browser supports it and otherwise in the first of WebM with Opus, MP4 and Ogg that it does (Safari records MP4). Save is an async button: it awaits onRecordingComplete, which gets the Blob with its duration in seconds and its mimeType, shows a spinner and then a check with "Saved", and the take becomes read-only: playback only, with a quiet Saved mark where the actions were. If the promise rejects, the button shows the error and the take stays unsaved, so it can be saved again. Pass saved={false} to give a saved take back its discard and Save (to re-record, say); saved is controlled when you pass it. Nothing plays by itself: playback starts only from the play button.
Discard keeps the take for a few seconds behind an Undo link; onDiscard fires once the discard is final (the window closes, a new take starts, or the recorder unmounts). A take passed as value (a Blob or a URL) opens saved, for playback only.
The microphone, the analyzer and the recorder are released when a take stops and when the recorder unmounts, so the browser's recording indicator goes out. getStream replaces the microphone with any MediaStream (a chosen device, a tab's audio, or the simulated voice these demos use).
If the browser refuses the microphone, finds none, or can't record at all, the caption says so and what to do, and the record button tries again. A permission refused earlier shows before the first press where the browser reports it.
Description
A message composer's voice note: record, listen back, then Save. The microphone is a synthesized hum here, and the upload is simulated.
maxDuration={15}: the ring around stop runs down, turns red for the last ten seconds, and the take stops at the limit.
Code
audio-recorder-demo.tsx, the Voice note story. The component itself comes from the package.
Reference
PropsonRecordingComplete, maxDuration, mimeType, value, saved, onDiscard… (12)
Props
| Prop | Type | |
|---|---|---|
onRecordingComplete | (blob: Blob, info: AudioRecordingInfo) => unknown | Promise<unknown>Save: the take, its length and type. Awaited: a rejection keeps the take unsaved and shows the error on the button. | – |
maxDuration | numberThe longest take, in seconds: a ring counts down and recording stops there. | – |
mimeType | stringThe container to record in, when the browser supports it; otherwise the first of WebM/Opus, MP4 and Ogg that it does. | – |
disabled | boolean | – |
size | Extract<Size, 'sm' | 'default'>default 'default' | 'default' |
value | Blob | string | nullA recording to show, as a Blob or a URL: it opens on playback, saved. | – |
saved | booleanWhether the take is saved, and so read-only (controlled). Default: true once Save succeeds or a | – |
onDiscard | () => voidWhen a discard is final: the Undo window closed, a new take started, or the recorder left. | – |
sound | booleanPlays toggleOn and toggleOff around a take, and Save's success or error. default true | true |
getStream | () => Promise<MediaStream>Where the sound comes from. Default: the microphone through getUserMedia. | – |
label | stringThe group's name. Default: the labels' "Audio recorder". | – |
locale | stringFormats the times. Default: the YoujProvider locale. | – |
Other props pass through: extends ComponentProps<'div'>. | ||
StylingParts and data attributes
Styling
The root audio-recorder carries data-state (idle, requesting, recording, paused, recorded or saved), data-size (sm or default) and data-disabled. Its parts are audio-recorder-stage (the live wave, audio-recorder-wave, or under reduced motion the meter, audio-recorder-meter, then the scrubber), audio-recorder-controls, audio-recorder-primary (record, stop, play), audio-recorder-ring, audio-recorder-dot, audio-recorder-timer, audio-recorder-remaining, audio-recorder-pause, audio-recorder-time, audio-recorder-discard, audio-recorder-save (the AsyncButton), audio-recorder-saved (the read-only mark), audio-recorder-caption and audio-recorder-error. A lit meter bar has data-yj-lit.
MotionLive wave, countdown · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Live waveone bar per twentieth of a second, sliding toward the start between samples, so the strip scrolls with the sound | duration.instant80ms · every frame | 80msevery frame | |
| Recording dotbreathes while recording and holds still while paused | duration.slow340ms | 340ms | |
| Countdown ringruns down with the time left; accent, then danger for the last ten seconds | duration.instant80ms · every frame | 80msevery frame | |
| Take opensthe scrubber’s bars rise from the midline from the start | duration.deliberate560ms | 560ms |
Reduced motion
A still level meter that lights up with the sound instead of the scrolling bars; the dot holds still.
Reduced motion
The wave doesn't scroll: a row of bars of rising height lights up to the current level, so you can still see that the microphone hears you. The recording dot holds still, and the scrubber's bars are drawn in place.
AccessibilityNames, timer, announcements, keyboard
Accessibility
- The recorder is a group named by
label(default "Audio recorder"). Every button is named: Start recording, Stop recording, Pause recording, Resume recording, Play, Pause, Discard recording, Save. - The record button keeps its place and its focus as it becomes stop and then play, so Space starts a take, stops it and plays it back without moving.
- The recording clock is a
role="timer"named "Recording time"; it is not announced every second. - Starting, pausing, stopping (with the take's length), reaching the limit, discarding and restoring are announced politely, as are the microphone messages.
- After a discard, an undo or a save, focus returns to the record or play button.
- Save announces its progress and outcome through the AsyncButton ("Saving…", "Saved").
- The waves are decorative; the scrubber is a slider with the time and length as its value.
- Time runs left to right in every script, like the scrubber; the controls follow the page's direction.
- Its words come from the labels (
audioRecorder…, withplay,pauseandundo), translated by the language packs.