<script setup lang="ts">
import type { ElevenLabs } from '@elevenlabs/elevenlabs-js'
import { VoicePicker } from '@/components/elevenlabs-ui/voice-picker'
import { ref } from 'vue'
const voices: ElevenLabs.Voice[] = [
{
voiceId: '21m00Tcm4TlvDq8ikWAM',
name: 'Rachel',
category: 'premade',
labels: {
accent: 'american',
descriptive: 'casual',
age: 'young',
gender: 'female',
language: 'en',
use_case: 'conversational',
},
description:
'Matter-of-fact, personable woman. Great for conversational use cases.',
previewUrl:
'https://storage.googleapis.com/eleven-public-prod/premade/voices/21m00Tcm4TlvDq8ikWAM/b4928a68-c03b-411f-8533-3d5c299fd451.mp3',
},
{
voiceId: '29vD33N1CtxCmqQRPOHJ',
name: 'Drew',
category: 'premade',
labels: {
accent: 'american',
description: 'well-rounded',
age: 'middle_aged',
gender: 'male',
use_case: 'news',
},
previewUrl:
'https://storage.googleapis.com/eleven-public-prod/premade/voices/29vD33N1CtxCmqQRPOHJ/b99fc51d-12d3-4312-b480-a8a45a7d51ef.mp3',
},
{
voiceId: '2EiwWnXFnvU5JabPnv8n',
name: 'Clyde',
category: 'premade',
labels: {
accent: 'american',
descriptive: 'intense',
age: 'middle_aged',
gender: 'male',
language: 'en',
use_case: 'characters_animation',
},
description: 'Great for character use-cases',
previewUrl:
'https://storage.googleapis.com/eleven-public-prod/premade/voices/2EiwWnXFnvU5JabPnv8n/65d80f52-703f-4cae-a91d-75d4e200ed02.mp3',
},
]
const selectedVoice = ref('21m00Tcm4TlvDq8ikWAM')
const open = ref(false)
function handleValueChange(value: string) {
selectedVoice.value = value
}
</script>
<template>
<div class="w-full max-w-lg">
<VoicePicker
:voices="voices"
:model-value="selectedVoice"
:open="open"
placeholder="Select a voice..."
@update:model-value="handleValueChange"
@update:open="open = $event"
/>
</div>
</template>Installation
pnpm dlx elevenlabs-ui-vue@latest add voice-picker
Usage
import { VoicePicker } from "@/components/elevenlabs-ui/voice-picker"Basic Usage
<script setup lang="ts">
import { ElevenLabs } from "@elevenlabs/elevenlabs-js"
const voices = [
{
voice_id: "21m00Tcm4TlvDq8ikWAM",
name: "Rachel",
preview_url: "https://example.com/rachel-preview.mp3",
// ... other voice properties
},
// ... more voices
]
const selectedVoice = ref("")
function handleValueChange(value: string) {
selectedVoice.value = value
}
</script>
<template>
<VoicePicker
:voices="voices"
v-model="selectedVoice"
@update:model-value="handleValueChange"
/>
</template>Controlled vs Uncontrolled
<script setup lang="ts">
import { VoicePicker } from '@/components/elevenlabs-ui/voice-picker'
const voices = ref([])
const selectedVoice = ref('')
</script>
<template>
<!-- Controlled -->
<VoicePicker
:voices="voices"
v-model="selectedVoice"
/>
<!-- Uncontrolled -->
<VoicePicker
:voices="voices"
@update:model-value="(voiceId) => console.log('Selected:', voiceId)"
/>
</template>Control Open State
<script setup lang="ts">
const voices = ref([])
const selectedVoice = ref('')
const open = ref(false)
function handleValueChange(value: string) {
selectedVoice.value = value
}
</script>
<template>
<VoicePicker
:voices="voices"
v-model="selectedVoice"
:open="open"
@update:model-value="handleValueChange"
@update:open="open = $event"
/>
</template>Custom Placeholder
<template>
<VoicePicker
:voices="voices"
v-model="selectedVoice"
placeholder="Choose a voice..."
/>
</template>Fetching Voices from ElevenLabs API
<script setup lang="ts">
import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js'
const voices = ref<Voice[]>([])
const selectedVoice = ref('')
onMounted(async () => {
const client = new ElevenLabsClient({
apiKey: import.meta.env.ELEVENLABS_API_KEY,
})
const response = await client.voices.getAll()
voices.value = response.voices
})
</script>
<template>
<VoicePicker
:voices="voices"
v-model="selectedVoice"
/>
</template>API Reference
VoicePicker
A searchable dropdown for selecting ElevenLabs voices with audio preview and orb visualization.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| voices | ElevenLabs.Voice[] | - | Required. Array of ElevenLabs voices |
| modelValue | string | - | Selected voice ID (controlled) |
| placeholder | string | "Select a voice..." | Placeholder text when no voice selected |
| class | string | - | Optional CSS classes for the trigger button |
| open | boolean | - | Control popover open state |
Emits
| Event | Type | Description |
|---|---|---|
| update:modelValue | (value: string) => void | Callback when selection changes |
| update:open | (open: boolean) => void | Callback when popover open state changes |
Features
- Search Functionality: Filter voices by name with built-in search
- Audio Preview: Play voice samples with play/pause controls
- Orb Visualization: Visual representation of each voice with the Orb component
- Keyboard Navigation: Full keyboard support for accessibility
- Controlled/Uncontrolled: Supports both controlled and uncontrolled patterns
- ElevenLabs Integration: Works seamlessly with ElevenLabs Voice API
- Audio Player: Integrated audio playback with shared state management
Notes
- Built on top of Command, Popover, and AudioPlayer components
- Requires
@elevenlabs/elevenlabs-jsfor ElevenLabs Voice types - Each voice displays with an Orb visualization and preview audio
- Audio playback is managed by AudioPlayer for consistent state
- Search is case-insensitive and filters by voice name
- Supports both controlled (
modelValue/onUpdate:modelValue) and uncontrolled modes - Open state can be controlled externally via
open/onUpdate:openprop and event - Keyboard accessible with standard combobox patterns
- Preview URLs from ElevenLabs Voice objects are used for audio playback