Voice Dictation
experimentalMic button that expands into a live waveform pill with a timer and live transcript; speech-to-text with recording fallback, toggle or hold-to-talk.
Toggle with live transcript
minimal
Toggle or hold, hotkey and collected notes
Switch modes, use ⌘⇧D, and watch final transcripts collect below.
full
ToggleControl Shift DCtrlShiftD
Status: idle
- Dictated notes appear here.
Dictating into a composer
Interim words preview in the placeholder; the final transcript is appended to the composer value.
composition
Installation
npx shadcn@latest add https://kuro.abstergo.fyi/r/voice-dictation.jsonCopies the source into components/kura/voice-dictation with its dependencies (kura-theme, audio-waveform, kura-keys, kura-motion, shimmer-text, spinner, tooltip, use-audio-analyser, use-hotkey, use-media-recorder, use-reduced-motion, use-speech-recognition, utils). Tokens come from the kura-theme item.
When to use it
Use it for
- Let users dictate a prompt instead of typing — standalone or in a composer toolbar.
- Push-to-talk input (mode="hold") for voice notes or quick commands.
- Browsers without the Web Speech API: record audio and transcribe on your server via `transcribe`.
Reach for something else when
- A full-duplex conversation with a voice agent (barge-in, spoken replies) — use agent-orb with your realtime session.
- Only visualising audio you already have — use audio-waveform.
- Typing prompts — use composer.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| onTranscript | (text: string) => void | — | Called once per dictation with the final trimmed text. Not called when cancelled (× or Escape) or when nothing was heard. |
| onInterim | (text: string) => void | — | Live text (final + interim) while listening, e.g. to preview in a placeholder. |
| onStatusChange | (status: VoiceDictationStatus) => void | — | idle | requesting | listening | processing | denied | unsupported | error. |
| mode | "toggle" | "hold" | "toggle" | toggle = click to start/stop; hold = dictate while the pointer, Space/Enter or the hotkey is held. |
| hotkey | readonly KeyName[] | null | ["mod", "shift", "d"] | Global shortcut (works while typing). null disables it. |
| lang | string | — | BCP-47 language for recognition, e.g. "en-US". Defaults to the page language. |
| transcribe | (audio: Blob) => Promise<string> | — | Fallback transcription when the Web Speech API is missing (Firefox): receives the recording; status is “processing” until it resolves. |
| simulate | boolean | string | — | Demo mode: fake permission, simulated waveform and a scripted transcript (a string is typed out word by word). For docs and tests. |
| showTranscript | boolean | true | Show the live transcript card under the control. Turn off inside a composer. |
| transcriptPlaceholder | string | "Listening…" | Text in the transcript card before words arrive. |
| disabled | boolean | false | Disable the control. |
All native attributes of the root element, className and ref pass through.
Anatomy
- VoiceDictationRoot column. data-status (see onStatusChange) and data-mode.
data-slot="voice-dictation" - VoiceDictationControlThe mic button; while live it grows (layout spring) into a signal-tinted pill with waveform, timer and cancel.
data-slot="voice-dictation-control" - VoiceDictationButtonMic / stop / mic-off button with aria-pressed and a tooltip showing the hotkey.
data-slot="voice-dictation-button" - VoiceDictationCancel× button that discards the dictation.
data-slot="voice-dictation-cancel" - VoiceDictationTranscriptLive transcript: final words in ink, interim words subtle, a signal caret while listening.
data-slot="voice-dictation-transcript" - VoiceDictationMessageRecovery text for denied / unsupported / error.
data-slot="voice-dictation-message"
Accessibility
- The button is labelled “Dictate” (or “Hold to dictate”) with aria-pressed reflecting listening; the stop/mic icon swap is visual only.
- A visually hidden role=status announces state changes once (“Listening”, “Transcribing”, “Microphone access is blocked”) — never per word.
- Denied and unsupported states keep a visible text explanation and the button stays reachable to retry.
- The waveform is decorative (aria-hidden); the timer is plain text with tabular numbers.
- Reduced motion: The pill appears without the layout spring, icon swaps fade without scale or blur, the waveform becomes a slow level meter and the caret stops blinking.
| Keys | Action |
|---|---|
| Enter / Space | Toggle dictation (hold them in hold mode) |
| Mod+Shift+D | Global toggle / hold (configurable via hotkey) |
| Escape | Cancel and discard while listening |
Notes for agents
- In docs, demos and tests pass `simulate` — real recognition needs a user gesture, mic permission and (for Chrome) a network connection.
- Feed a Composer by controlling its value: onTranscript={(t) => setValue((v) => (v ? v + ' ' + t : t))}; pass showTranscript={false}.
- Provide `transcribe` for Firefox/other browsers without SpeechRecognition; without it those browsers record but emit nothing.
- Only one mounted instance should own a given hotkey; pass hotkey={null} on the others.
- The listening pill uses the jade signal — avoid another accent-filled element in the same view while dictating.