Skip to content

Voice Selection

The second argument to speak() is a voice selector — a string, RegExp, or structured query that tells ResponsiveVoice which voice to use:

rv.speak(text: string, voice?: VoiceSelectorInput, params?: SpeakOptions): void;

VoiceSelector (the post-parse, on-the-wire form) is a union of three JSON-serializable shapes:

TypeJS formWire formDescription
string'UK English Female'sameExact voice name
RegexSelector/Portuguese/i{ regex: 'Portuguese', flags: 'i' }First voice matching the pattern
VoiceQuery{ lang: 'pt' }sameStructured filter (AND logic)

In JS code, pass a real RegExp literal and the schema normalizes it to the JSON-clean { regex, flags } form on parse — the resolver, server payloads, and SDKs all see the wire form, so the contract is identical across every language.

The simplest form — pass a ResponsiveVoice voice name as a string:

rv.speak('Hello', 'UK English Female');
rv.speak('Hello', 'US English Male');
rv.speak('Bonjour', 'French Female');
rv.speak('Hallo', 'Deutsch Male');

If the exact name isn't available on the current platform, the voice matching chain kicks in to find the closest alternative.

Pass a RegExp to match against all non-deprecated voice names. The first match wins:

rv.speak('Olá', /Portuguese/);
rv.speak('Hello', /English.*Female/i);

In server-side config or non-JS SDKs (Python, Go, PHP, Java), use the JSON literal form instead — it's the same selector after the schema normalizes:

{ "regex": "Portuguese" }
{ "regex": "English.*Female", "flags": "i" }

Pass a VoiceQuery object to filter voices by attributes. All conditions are AND-ed — a voice must match every specified field:

FieldTypeBehavior
namestringCase-insensitive; exact match first, then substring
langstringBCP-47 prefix match ("pt" matches "pt-BR")
gender'f' | 'm' | 'male' | 'female'Gender filter
isByokbooleanFilter to BYOK (Bring Your Own Key) voices only
providerstringProvider name, case-insensitive
// By language
rv.speak('Bonjour', { lang: 'fr' });
// By language + gender
rv.speak('Olá', { lang: 'pt', gender: 'f' });
// By provider (BYOK voices)
rv.speak('Hello', {
provider: 'Google Cloud WaveNet',
lang: 'en-GB',
gender: 'm',
});

When you have a raw SpeechSynthesisVoice object from the browser's Web Speech API, you can pass it directly via the params.voice option:

const nativeVoices = speechSynthesis.getVoices();
const samantha = nativeVoices.find((v) => v.name === 'Samantha');
rv.speak('Hello', undefined, { voice: samantha });

When no voice selector is passed to speak(), the configured default voice is used. The built-in default is 'UK English Female'.

// Set at init
const rv = await getResponsiveVoice({
apiKey: 'YOUR_KEY',
defaultVoice: 'US English Female',
});
rv.speak('Uses US English Female');
// Change at runtime
rv.setDefaultVoice('French Male');
rv.speak('Uses French Male now');

ResponsiveVoice accepts a language query, but your interface decides when to change languages. A multilingual website widget should normally use this precedence:

  1. A voice explicitly chosen by the visitor
  2. Language detected from text the visitor typed
  3. The browser's preferred language
  4. English fallback using UK English Female
function browserLanguage(): string {
return (navigator.languages?.[0] || navigator.language || 'en')
.toLowerCase()
.split('-')[0];
}
function selectorForLanguage(language: string) {
const lang = language.toLowerCase().split('-')[0];
// Keep the English experience consistent and premium by default.
if (lang === 'en') return 'UK English Female';
return { lang, gender: 'f' } as const;
}
function speakVisitorText(text: string, selectedVoice?: string) {
// Supply this with your preferred client-side or server-side detector.
const typedLanguage = text.length >= 20 ? detectLanguage(text) : undefined;
const language = typedLanguage || browserLanguage();
const voice = selectedVoice || selectorForLanguage(language);
rv.speak(text, voice);
}

Use browser language to initialize placeholder text and the first suggested voice. Re-run language detection after the visitor types enough text to make a useful decision, but do not override a manual voice selection.

Set forceFallback to skip native browser voices entirely and always use server-side HTTP audio. This provides consistent voice quality across all browsers:

const rv = await getResponsiveVoice({
apiKey: 'YOUR_KEY',
forceFallback: true,
});
// Toggle at runtime
rv.setForceFallback(false);

When speak() is called, voice selection resolves in this order (highest priority first):

  1. params.voice override — direct SpeechSynthesisVoice object; bypasses everything below
  2. resolveVoice hook — intercepts and transforms the selector before resolution
  3. Explicit VoiceSelector — the string, RegExp (or its { regex, flags? } wire form), or VoiceQuery passed to speak()
  4. defaultVoice config — used when no selector is provided

Once a voice name is determined, ResponsiveVoice walks the voice's internal chain of system voice IDs and tries these matching strategies in order across all chain entries (strategy-first):

  1. Exact match — direct name comparison against browser voices
  2. Whitespace normalized — handles Chrome's Unicode non-breaking spaces (U+00A0) in Asian voice names
  3. Parenthetical stripped — handles Apple Safari's "(Enhanced)" / "(Premium)" suffixes
  4. Partial match — case-insensitive substring match
  5. Language fallback — any browser voice matching the target language
  6. HTTP fallback — server-side TTS via the ResponsiveVoice API

Use getVoices() to list all available voices on the current platform:

const voices = rv.getVoices();
console.log(voices);
// [
// { name: 'UK English Female', lang: 'en-GB', gender: 'f' },
// { name: 'UK English Male', lang: 'en-GB', gender: 'm' },
// { name: 'US English Female', lang: 'en-US', gender: 'f' },
// ...
// ]

Some voices are only available on specific platforms:

PlatformNotes
ChromeGoogle voices, best selection
SafariApple voices, "(Enhanced)" variants
FirefoxLimited native voices, uses fallback
iOSVersion-specific voice sets
AndroidDevice-dependent voices

Voices use BCP-47 language codes:

CodeLanguage
en-GBBritish English
en-USAmerican English
fr-FRFrench
de-DEGerman
es-ESSpanish
ja-JPJapanese
zh-CNChinese (Simplified)

Try all three selector forms side by side in the live Voice Selector demo — type a name, type a regex, or build a query and watch the snippet rewrite as you switch tabs. The accompanying docs page explains the design choices.

The minimal pattern below shows the simplest case (build a dropdown from getVoices()):

// Build a voice selector dropdown
const select = document.createElement('select');
rv.getVoices().forEach((voice) => {
const option = document.createElement('option');
option.value = voice.name;
option.textContent = `${voice.name} (${voice.lang})`;
select.appendChild(option);
});
select.addEventListener('change', () => {
rv.speak('Sample text', select.value);
});