Voice picker
A list of voices to choose from, each with a short preview you can play.
Loading preview…
Install
- 1
Run this command in your Tailwind CSS project. It adds
components/ai/voice-picker.tsxand installs its dependencies.$ npx shadcn@latest add https://gradientdeck.com/ai-components/r/voice-picker.json
Usage & API
Example
import { VoicePicker } from "@/components/ai/voice-picker"
<VoicePicker
voices={[]}
selectedId="…"
onSelect={() => {}}
onPreview={() => {}}
/>Props VoicePicker
| Prop | Type & description | Required |
|---|---|---|
voices | Voice[] | Required |
selectedId | string | Required |
onSelect | (id: string) => void | Required |
playingId | string | nullThe voice whose preview is playing, if any. | Optional |
onPreview | (id: string | null) => void | Required |
previewLength | numberPreview length in seconds, used for the progress ring. | Optional |
label | string | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Grid is the only variant. The radiogroup is labelled by the header. Only the selected radio is in the tab order. The others are skipped until an arrow moves selection, which is the usual radio pattern. The play button is a separate control, so it stays clickable above the radio's full-cell hit area. Its label is Play preview of or Stop preview of, plus the voice name. The ring is decorative and omitted under reduced motion. The mini waveform is decorative too. It renders only while that id is playingId. Avatars are radial gradients from the two colours on the voice object. The check badge is a small warm circle with a tick, ringed so it reads on both light and dark surfaces. The demo does not play a sound file. It only sets playingId and clears it after 3.5 seconds.
Choosing a layout
Use it when the assistant's voice is a choice someone should hear before they commit. A settings page or the step before the first call is the right place. The play button is deliberately on each row so a person can sample a voice without selecting it and without leaving the grid. Keep the clip short. The ring is drawn for previewLength and the demo stops at that same duration. If your clip is longer, pass the real length or the ring will finish early and lie. Voice session does not include this grid. Drop the person back here to change voice, then return them to the call bar.
Do not use it to choose a microphone. Microphone picker is the input list and the level meter. Do not use it as the live visual. Voice orb and the aura inside Voice session show listening, thinking, and speaking. This grid shows identity: name, tags, and a sample. It is a poor fit for a long catalog with hundreds of voices, because there is no query box and the layout is a simple wrap of cards. A handful to a few dozen is what the two-column grid is built for. If preview audio is not available yet, you can still render the grid and ignore the play button, but the button will always be drawn.
Does the preview button play audio by itself?
No. It calls onPreview with the voice id, or null to stop. You play the clip, then pass that id back as playingId so the pause icon, the ring, and the mini waveform appear. The ring's duration is previewLength. The component never creates an audio element.
How does keyboard selection work?
The cells are radios. The selected one is tabbable. Arrow keys, including up and down, move by one voice, wrap at the ends, call onSelect, and move focus. They do not activate preview. Preview is the round button, reached after the radio in tab order, and it toggles on click or on its own key activation.