Web Speech API: Simple Browser Voice Demo

- The Web Speech API has two independent parts:
SpeechSynthesis for text-to-speech and SpeechRecognition for speech-to-text.- The text-to-speech demo in this guide needs no library, API key, microphone permission or application backend.
- Browser-native does not always mean device-only. Some speech-recognition implementations send audio to a remote service.
- Speech synthesis is broadly available across current browsers, but available voices differ by browser, operating system and installed language packs.
- Speech recognition has more limited and inconsistent browser support, so it should be treated as an enhancement rather than the only way to complete a task.
- Always use feature detection, start speech from a clear user action, provide Stop and fallback controls, and handle
end and error events.- A “read aloud” button can improve usability, but it is not a replacement for semantic HTML or screen-reader support.
The Web Speech API lets a web page speak text aloud and, in supporting browsers, convert a user's speech into text. We can access both capabilities with JavaScript, without installing a package, creating an API key, or building a speech-processing backend.
That does not mean every operation happens privately or offline. Speech synthesis normally uses voices provided by the browser, operating system, or an installed speech service. Speech recognition may use an online recognition service and send captured audio away from the device. The API gives us a browser interface, but the browser decides how the underlying speech engine is implemented.
In this guide, we will build a working text-to-speech demo, explain the two parts of the Web Speech API, add voice and playback controls, and cover the browser support, privacy and accessibility details that matter outside a quick experiment.
What Is the Web Speech API?
The Web Speech API is a JavaScript API for adding speech input and speech output to web pages. Its two main interfaces solve different problems:
| Capability | Main interface | Direction | Typical use |
| Speech synthesis | SpeechSynthesis | Text to spoken audio | Read-aloud controls, confirmations and narration |
| Speech recognition | SpeechRecognition | Spoken audio to text | Dictation, commands and voice-assisted form input |
The interfaces can be used separately. A website that only needs to read instructions aloud does not need microphone access or speech recognition. Likewise, a dictation feature can use recognition without producing synthesized speech.
It is also more accurate to say that the Web Speech API avoids a speech-service integration than to say it uses “no AI.” The browser or operating system may use machine-learning models or a remote service internally. We simply do not have to select, host or call that model ourselves.
Try It: A Browser Text-to-Speech Demo
Save the following code as speech-demo.html and open it in a supporting browser. Enter text, choose an available voice, adjust the rate or pitch, and select Speak.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Web Speech API Demo</title>
</head>
<body>
<main>
<h1>Browser Voice Demo</h1>
<label for="speechText">Text to read</label>
<textarea id="speechText" rows="5">
Hello! This voice is being generated through the Web Speech API.
</textarea>
<label for="voiceSelect">Voice</label>
<select id="voiceSelect">
<option value="">System default</option>
</select>
<label for="rate">
Rate: <output id="rateValue">1</output>
</label>
<input
id="rate"
type="range"
min="0.5"
max="2"
value="1"
step="0.1"
/>
<label for="pitch">
Pitch: <output id="pitchValue">1</output>
</label>
<input
id="pitch"
type="range"
min="0"
max="2"
value="1"
step="0.1"
/>
<div>
<button id="speakButton" type="button">Speak</button>
<button id="stopButton" type="button">Stop</button>
</div>
<p id="status" role="status" aria-live="polite">Ready.</p>
</main>
<script>
const speechText = document.querySelector("#speechText");
const voiceSelect = document.querySelector("#voiceSelect");
const rate = document.querySelector("#rate");
const pitch = document.querySelector("#pitch");
const rateValue = document.querySelector("#rateValue");
const pitchValue = document.querySelector("#pitchValue");
const speakButton = document.querySelector("#speakButton");
const stopButton = document.querySelector("#stopButton");
const status = document.querySelector("#status");
const speechSupported =
"speechSynthesis" in window &&
"SpeechSynthesisUtterance" in window;
let voices = [];
function setStatus(message) {
status.textContent = message;
}
function loadVoices() {
voices = window.speechSynthesis.getVoices();
const selectedVoice = voiceSelect.value;
voiceSelect.replaceChildren();
voiceSelect.add(new Option("System default", ""));
for (const voice of voices) {
const label = `${voice.name} (${voice.lang})`;
voiceSelect.add(new Option(label, voice.voiceURI));
}
if (voices.some((voice) => voice.voiceURI === selectedVoice)) {
voiceSelect.value = selectedVoice;
}
}
rate.addEventListener("input", () => {
rateValue.value = rate.value;
});
pitch.addEventListener("input", () => {
pitchValue.value = pitch.value;
});
if (!speechSupported) {
speakButton.disabled = true;
stopButton.disabled = true;
voiceSelect.disabled = true;
setStatus("Text-to-speech is not supported in this browser.");
} else {
loadVoices();
window.speechSynthesis.addEventListener("voiceschanged", loadVoices);
speakButton.addEventListener("click", () => {
const text = speechText.value.trim();
if (!text) {
setStatus("Enter some text before selecting Speak.");
speechText.focus();
return;
}
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
const selectedVoice = voices.find(
(voice) => voice.voiceURI === voiceSelect.value,
);
if (selectedVoice) {
utterance.voice = selectedVoice;
utterance.lang = selectedVoice.lang;
} else {
utterance.lang = document.documentElement.lang || "en-US";
}
utterance.rate = Number(rate.value);
utterance.pitch = Number(pitch.value);
utterance.volume = 1;
utterance.addEventListener("start", () => {
setStatus("Speaking…");
});
utterance.addEventListener("end", () => {
setStatus("Finished speaking.");
});
utterance.addEventListener("error", (event) => {
if (event.error === "canceled" || event.error === "interrupted") {
return;
}
setStatus(`Speech failed: ${event.error}.`);
});
window.speechSynthesis.speak(utterance);
});
stopButton.addEventListener("click", () => {
window.speechSynthesis.cancel();
setStatus("Speech stopped.");
});
}
</script>
</body>
</html>This remains a small demo, but it avoids several problems found in the common five-line example: it detects support, handles an initially empty voice list, prevents repeated clicks from building a long queue, reports errors and gives the user a Stop control.
How the Text-to-Speech Code Works
The browser exposes one speech-synthesis controller through window.speechSynthesis. We pass speech requests to that controller as SpeechSynthesisUtterance objects.
const utterance = new SpeechSynthesisUtterance("Welcome to the application");
window.speechSynthesis.speak(utterance);The utterance holds the text and its speaking options. The controller manages playback and its queue.
Choosing a voice
speechSynthesis.getVoices() returns the voices currently available on the device. A voice includes properties such as its name, language and voiceURI.
Voice loading is not identical across browsers. The first call to getVoices() may return an empty array while the browser loads its voice list. Listening for the voiceschanged event lets the page populate the selector when that list becomes available.
The exact voices cannot be assumed. A voice present on a developer's macOS machine may not exist on Windows, Android or another user's browser. Production interfaces should offer the available list or allow the system default instead of hard-coding one voice name.
Controlling rate, pitch and volume
SpeechSynthesisUtterance exposes three useful playback controls:
| Property | Specification range | Default | Purpose |
rate | 0.1 to 10 | 1 | Changes speaking speed |
pitch | 0 to 2 | 1 | Changes perceived pitch |
volume | 0 to 1 | 1 | Changes utterance volume |
Individual voices or synthesis engines may apply narrower practical limits. The demo intentionally restricts the rate slider to 0.5–2, which is easier for users to control than the full allowed range.
Cancelling queued speech
Calls to speechSynthesis.speak() are queued. If a user selects Speak repeatedly, several utterances can accumulate. Calling speechSynthesis.cancel() before starting a new utterance clears the current speech and anything waiting in the queue.
Let’s Build Voice-Enabled Web Apps Together!
We build web apps with real voice interaction using the Web Speech API, fast, smart, and accessible.
Whether cancellation is desirable depends on the interface. A notification reader may need a deliberate queue; an interactive preview normally should replace the previous utterance.
Handling speech events
An utterance can report start, end, error, pause, resume and boundary events. At minimum, production code should handle end and error so the interface does not remain stuck in a “speaking” state.
The status paragraph in the demo uses role="status" and aria-live="polite". This makes status changes available to assistive technology without moving keyboard focus away from the controls.
Speech Recognition: Converting Voice to Text
The recognition side listens to an audio source and returns recognized words through events. Its browser support is less consistent than speech synthesis, and some browsers still expose the prefixed webkitSpeechRecognition constructor.
Here is a minimal one-shot recognition example:
<button id="listenButton" type="button">Start listening</button>
<p id="transcript" role="status" aria-live="polite">
Your transcript will appear here.
</p>
<script>
const Recognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
const listenButton = document.querySelector("#listenButton");
const transcript = document.querySelector("#transcript");
if (!Recognition) {
listenButton.disabled = true;
transcript.textContent =
"Speech recognition is not supported in this browser.";
} else {
const recognition = new Recognition();
recognition.lang = document.documentElement.lang || "en-US";
recognition.continuous = false;
recognition.interimResults = false;
recognition.maxAlternatives = 1;
recognition.addEventListener("start", () => {
listenButton.disabled = true;
transcript.textContent = "Listening…";
});
recognition.addEventListener("result", (event) => {
transcript.textContent = event.results[0][0].transcript;
});
recognition.addEventListener("error", (event) => {
transcript.textContent = `Recognition failed: ${event.error}.`;
});
recognition.addEventListener("end", () => {
listenButton.disabled = false;
});
listenButton.addEventListener("click", () => {
recognition.start();
});
}
</script>Starting recognition can trigger a microphone-permission prompt. A denial, missing microphone, unsupported language, network problem or lack of recognized speech can all produce different errors, so an application should not treat every failure as “the user said nothing.”
continuous = false makes this a one-shot interaction. With continuous = true, the service may return several results until it is stopped, but behavior still varies by implementation. interimResults = true can provide provisional text while the user is speaking; that text should be presented as temporary until the corresponding result is final.
Is the Web Speech API Private or Offline?
There is no single answer for both halves of the API.
Speech synthesis
Speech synthesis commonly uses voices supplied by the browser, operating system or installed speech engine. Some voices may be local, while the availability and implementation of network-backed voices depend on the platform. We should not promise offline operation without testing the chosen browser, operating system and voice.
Speech recognition
Recognition deserves a clearer warning. In some browsers, including implementations documented by MDN, audio is sent to a server-based recognition service. That means recognition may require an internet connection and may involve transmitting a user's speech to a platform provider.
Newer Web Speech API work includes on-device recognition controls such as processLocally, language availability checks and language-pack installation. These capabilities are not yet a safe cross-browser baseline. They require their own feature detection, and on-device recognition can fail when the required language pack is unavailable or blocked by Permissions Policy.
For sensitive speech—such as medical, financial, legal or internal company information—we should identify which engine processes the audio, where processing occurs, what the platform retains and whether that behavior meets the application's consent and compliance requirements. “No API key” is not a privacy guarantee.
Browser Support and Fallbacks
Current compatibility documentation describes speech synthesis as broadly available, while SpeechRecognition remains limited and is not part of the Baseline set of features supported across major browsers.
Feature detection is therefore more reliable than browser-name checks:
const canSpeak =
"speechSynthesis" in window &&
"SpeechSynthesisUtterance" in window;
const Recognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
const canRecognize = Boolean(Recognition);Fallback design depends on the feature:
- Keep visible text available when speech synthesis is missing or fails.
- Keep normal text fields and buttons available when voice input is missing.
- Never make speech recognition the only way to submit a form, navigate or confirm a destructive action.
- Preserve the user's work when recognition stops or permission is denied.
- Test the actual browser and operating-system combinations used by the audience, because available voices and recognition behavior are platform-dependent.
Accessibility: Useful Enhancement, Not a Screen Reader
A read-aloud control can help users who prefer auditory content, have reading difficulties or need hands-free feedback. It does not provide the navigation, semantics, focus management and announcements offered by a screen reader.
For an accessible implementation:
- start speech only after an intentional user action;
- provide pause, resume or stop controls when the content is long;
- retain the original text on screen;
- use real buttons and associated form labels;
- expose status and error messages to assistive technology;
- do not start unexpected speech when the page loads;
- respect the user's selected voice and playback preferences where possible;
- test with keyboard navigation and at least one screen reader.
Voice output should complement well-structured HTML, not compensate for missing headings, labels or alternative text.
When the Web Speech API Is a Good Fit
The API works well when the feature is optional and platform variation is acceptable. Examples include:
- reading a short instruction or confirmation aloud;
- previewing pronunciation;
- voice-assisted search or form entry;
- prototyping a conversational interface;
- adding spoken feedback to an educational activity;
- providing hands-free commands alongside standard controls.
Consider a dedicated speech service or native application capability when the product requires:
- consistent voices across every device;
- guaranteed offline behavior;
- controlled audio files that can be stored or downloaded;
- domain-specific recognition accuracy;
- speaker identification or diarization;
- custom voice models;
- auditable data location, retention and compliance controls;
- server-side transcription or large-scale batch processing.
Let’s Build Voice-Enabled Web Apps Together!
We build web apps with real voice interaction using the Web Speech API, fast, smart, and accessible.
The Web Speech API is valuable because it is small and immediately available—not because it replaces every speech platform.
Production Checklist
Before shipping a browser voice feature, check the following:
- Does the page detect synthesis and recognition separately?
- Is the feature still usable when either API is unavailable?
- Does speech start from a clear user action?
- Can the user stop or replace queued speech?
- Does the implementation wait for
voiceschangedwhere necessary? - Is the language set explicitly or inherited from the document?
- Are
end,error, permission denial and “no speech” states handled? - Are text and standard controls still available?
- Has recognition data flow been reviewed for privacy and compliance?
- Has the feature been tested across the target browser and operating-system combinations?
Frequently Asked Questions
Does the Web Speech API require an API key?
No. A page can call the browser interfaces without registering for an API key. The browser or operating system may still use an underlying platform or online speech service.
Does the Web Speech API use AI?
The JavaScript API does not require us to integrate an AI model. However, the browser's underlying speech engine may use machine learning. It is safer to describe the API as browser-provided than as completely “non-AI.”
Does text-to-speech require microphone permission?
No. Speech synthesis produces audio and does not need microphone input. Speech recognition listens for audio and can require the user's microphone permission.
Does the Web Speech API work offline?
It depends on the browser, operating system, selected voice, recognition mode and installed language resources. Speech recognition often uses an online service. Offline behavior should be treated as a tested platform capability, not a general API guarantee.
Why does speechSynthesis.getVoices() return an empty array?
Some browsers load the voice list asynchronously. Call getVoices() once, then update the interface when the voiceschanged event fires.
Why does speech recognition work in one browser but not another?
SpeechRecognition has limited and uneven implementation across browsers. Detect both SpeechRecognition and webkitSpeechRecognition, and always provide a non-voice alternative.
Can the Web Speech API create an audio file?
The standard speech-synthesis interface plays an utterance through the browser; it does not expose a portable audio file for download. Use another synthesis solution when generating and storing audio files is a requirement.
Final Thoughts
For a browser feature that reads a message aloud, the Web Speech API can be genuinely simple: create a SpeechSynthesisUtterance, pass it to speechSynthesis.speak(), and let the platform provide the voice.
The production work sits around those two lines. We need to load voices safely, prevent unwanted queues, expose playback state, provide fallbacks and avoid making promises the browser cannot guarantee. Speech recognition needs even more care because its support, network dependency and privacy behavior vary between implementations.
Start with the text-to-speech demo, test it on the devices that matter, and keep voice interaction optional. That gives users a useful enhancement without making the rest of the interface depend on an inconsistent browser capability.



