Search documentation

Find a vivid-layer page or product.

Voice Composer

Switches between a controlled text composer and browser-backed voice capture with a real-time waveform.

pnpm dlx shadcn@latest add @vivid-layer/voice-composer

Try me!

Waveform

Voice Composer is a controlled message form with two views: a text composer and a live voice recorder. It owns microphone capture, browser capability routing, the recording lifecycle, and transcript insertion. Your application owns the message draft, submission, and the fallback transcription service.

The component reuses LineWaveform for both real microphone input and the processing state. Change waveformMode to choose a centered static spectrum or a waveform that scrolls from right to left.

Browser strategy

Chrome and Edge use the Web Speech API when it is available. Recognition runs continuously and may produce interim results, but Voice Composer only keeps final results and does not write them into the draft until the user selects Stop. LineWaveform still acquires the microphone stream in this path so the voice view shows real audio activity.

Other browsers use MediaRecorder with the same stream owned by LineWaveform. The component probes WebM/Opus, Ogg/Opus, then MP4 and passes the completed Blob to onAudioRecorded. Return the final transcript from that callback. When this callback is absent, the Voice action is unavailable on browsers that need the fallback.

API reference

PropTypeDefaultDescription
valuestringProvides the controlled message draft.
onValueChange(value: string) => voidReceives text edits and the draft with a completed transcript inserted.
onSubmit(value: string, event: React.FormEvent<HTMLFormElement>) => void | Promise<void>Receives the current draft and form event when the text composer submits.
onAudioRecorded(audio: Blob) => Promise<string>undefinedTranscribes the MediaRecorder fallback Blob and resolves with final text.
onTranscriptionChange(text: string) => voidundefinedReceives the completed transcript before it is inserted into the draft.
onError(error: { code: VoiceComposerErrorCode; message: string; cause?: unknown }) => voidundefinedReceives a structured capture, recognition, transcription, or empty-result error.
langstring"en-US"Sets the language passed to browser speech recognition.
disabledbooleanfalseDisables composing, voice capture, and submission.
maxDurationSecondsnumber120Sets the recording limit in seconds. Reaching it stops and transcribes the recording.
waveformMode"static" | "scrolling""scrolling"Forwards the visual mode to LineWaveform.

VoiceComposer also accepts standard form props except onSubmit and onError, which use the contracts above.

VoiceComposerErrorCode

type VoiceComposerErrorCode =
  | "unsupported"
  | "permission_denied"
  | "capture_failed"
  | "recognition_failed"
  | "transcription_failed"
  | "empty_transcript"

Behavior

Selecting Voice saves the textarea selection and starts microphone setup while the text composer remains visible. The composer moves to the voice view only after recording has actually started, so permission setup is not exposed as a third visual mode. Stop completes recognition or recording, then inserts the final transcript once at the saved selection and restores textarea focus. Cancel discards the current voice result without changing the draft.

Recording automatically stops after maxDurationSeconds. The transcription stage cannot be cancelled because the component cannot abort an arbitrary onAudioRecorded promise. All errors return to the text view, preserve the draft, and call onError; the component does not render an error message or toast.

The text and voice views move horizontally, and the inactive view becomes inert immediately so its controls cannot be activated during the transition. Transcription stays in the voice view and uses its processing waveform and status text instead of adding another action state. Motion respects the user's reduced-motion preference. A persistent polite live region announces capture and transcription status.