Voice Picker
Language-grouped TTS voice selection — a compact grouped select plus a card/grid variant with gender badge, voice id, search, and local preview playback.
Voice Picker
Voice Picker is TTS voice selection from one data contract. Ships three pieces:
VoicePicker— a compact<select>partitioned into<optgroup>by language, each option showing the voice name + a gender glyph.VoiceCard— a rich card: voice name, color-coded gender badge, monospace voice id, and a circular play/stop preview button with a loading state.VoiceGrid— language-grouped grid of cards with a count header and a search box.
All three consume one VoiceOption[] contract (id, name, gender, languageLabel), which matches the KokoroVoice shape from @localmode/transformers (29 English voices).
When to use it: let users pick — and preview — a Kokoro voice before synthesizing, in either a compact (select) or rich (grid) surface.
Preview
Installation
pnpm dlx shadcn@latest add @localmode/ui/audio/voice-pickernpx shadcn@latest add @localmode/ui/audio/voice-pickeryarn dlx shadcn@latest add @localmode/ui/audio/voice-pickerbunx --bun shadcn@latest add @localmode/ui/audio/voice-pickerData source & dependencies
Data source: renders the VoiceOption[] you pass and emits selection/preview events — works with any backend. Recommended producer: useSynthesizeSpeech for inline preview (execute(text, { voice }) overrides the voice per call) with the Kokoro TTS model + KOKORO_VOICES catalog from @localmode/transformers, via @localmode/react (on-device, optional).
clsx+tailwind-merge— via the sharedcn()util
Files installed
voice-picker.tsx—VoicePicker,VoiceCard,VoiceGridwaveform-activity-bars.tsx— used by the preview button's loading state (registry dependency)lib/utils.ts— thecn()helper (if not already present)
Props
VoicePicker
Prop
Type
VoiceGrid
Prop
Type
VoiceCard
Prop
Type
Examples
Compact select
import { VoicePicker } from '@/components/voice-picker';
import { KOKORO_VOICES } from '@localmode/transformers';
export function Example() {
const [voice, setVoice] = useState('af_heart');
return <VoicePicker voices={KOKORO_VOICES} value={voice} onValueChange={setVoice} />;
}Grid with local preview
import { VoiceGrid } from '@/components/voice-picker';
import { useSynthesizeSpeech } from '@localmode/react';
import { transformers, KOKORO_VOICES } from '@localmode/transformers';
export function VoiceBrowser() {
const [voice, setVoice] = useState<string>();
const [previewing, setPreviewing] = useState<string | null>(null);
const tts = useSynthesizeSpeech({ model: transformers.textToSpeech('onnx-community/Kokoro-82M-v1.0-ONNX') });
const preview = async (id: string) => {
setPreviewing(id);
const res = await tts.execute('Hello from LocalMode.', { voice: id }); // per-call voice override
if (res) new Audio(URL.createObjectURL(res.audio)).play();
setPreviewing(null);
};
return (
<VoiceGrid
voices={KOKORO_VOICES}
value={voice}
onValueChange={setVoice}
onPreview={preview}
loadingVoiceId={previewing}
/>
);
}useSynthesizeSpeech accepts voice / speed / pitch at the hook level and per call — execute(text, { voice }) is what makes inline per-voice previews possible from a single hook instance.
Customization
Voices are grouped by languageLabel, so any provider that exposes that field groups correctly. Gender badge colors use Tailwind's pink / sky palettes — swap them in the copied file to match your design system. The grid's search filters on name and id; widen the predicate in VoiceGrid if you add more searchable metadata.
Accessibility
A VoiceCard exposes its preview and selection as two distinct sibling buttons — it never nests an interactive element inside another (no <button> inside a role="button" card), which would be an invalid, non-operable a11y tree. The optional preview button plays a locally-synthesized sample; the selection button is labelled "Select {name}" and announces its state with aria-pressed, so screen readers convey which voice is active and both actions are independently focusable and keyboard-operable.
Waveform Activity Bars
A pure-CSS row of pulsing bars that doubles as an audio-processing indicator, an idle empty-state, and a volume-reactive voice-agent visualizer.
Streaming Speech Panel
Streaming TTS status surface — waveform + spinner + now-playing clause while synthesizing, then a clause count, local-privacy note, and Download WAV on completion.