General
Talk to your PWA and have it talk back — Speech Recognition & Synthesis (FieldKit companion)
Oleksandr Trukhnii DEV Community 周榜
3 views
A bonus capability for FieldKit, the field-notes PWA I built across this series (code on GitHub). The series wrapped at part 7, but I said the app didn't have to be done — and voice is too good a fit for a field tool to skip. Out in the field your hands are busy, so: dictate a note by talking, and have any note read back to you. Both come from the Web Speech API, and both are a handful of lines.
Two halves, very different maturity
The Web Speech API has two sides, and it's important to know up front that they're not equally mature:
SpeechSynthesis (text → speech): boring in the best way. Broadly supported, reliable, works offline with the platform's built-in voices.
SpeechRecognition (speech → text): powerful but uneven — great in Chromium and Safari, effectively absent in Firefox, and in Chrome it sends audio to a cloud service.
We'll do the easy, reliable one first.
Reading notes aloud with SpeechSynthesis
Text-to-speech is almost anticlimactically simple. Wrap a string in an utterance and speak it:
export function speak(text, { lang = "en-US" } = {}) {
if (!synthesisSupported()) throw new Error("Speech synthesis isn't supported here.");
window.speechSynthesis.cancel(); // don't stack utterances on repeated taps
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = lang;
window.speechSynthesis.speak(utterance);
}
In FieldKit every note gets a 🔊 button that reads its text.
The one non-obvious detail is speechSynthesis.cancel() first — without it, tapping twice queues two voices talking over each other. You can also set utterance.rate, pitch, and pick a specific voice from speechSynthesis.getVoices() (which, annoyingly, populates asynchronously — listen for the voiceschanged event if you need a particular voice).
Dictating notes with SpeechRecognition
The recognition side is the fun one. The constructor is still vendor-prefixed in most browsers, so normalise it:
const Recognition = window.SpeechRecognition || window.webkitSpeechRecognition;
Then configure and start. The two settings that matter most are interimResults (stream words as they're spoken, instead of waiting for a final result) and continuous (keep listening vs stop after one phrase):
const recognition = new Recognition();
recognition.lang = "en-US";
recognition.interimResults = true;
recognition.continuous = false;
recognition.onresult = (event) => {
let interim = "", final = "";
for (let i = event.resultIndex; i < event.results.length; i++) {
const result = event.results[i];
if (result.isFinal) final += result[0].transcript;
else interim += result[0].transcript;
}
onUpdate(final || interim, Boolean(final));
};
recognition.start();
The onresult loop is the part people get wrong. Results arrive incrementally: some are interim (still being refined) and some are final. You iterate from event.resultIndex, separate the two, and typically show interim text live but only commit final text. In FieldKit the 🎤 button streams the transcript straight into the note composer:
stopDictation = startDictation({
onUpdate: (text, isFinal) => {
els.text.value = dictationBase + text; // live interim preview
if (isFinal) dictationBase = els.text.value.trimEnd() + " "; // commit
},
onEnd: () => els.addDictation.classList.remove("btn--recording"),
});
So you watch your words appear as you speak, hands-free — exactly what you want when you're holding a trail map instead of a keyboard.
The honest support picture (this one really matters)
Speech is one of the most fragmented APIs on the platform, so be precise with users:
SpeechSynthesis: supported in Chrome, Edge, Firefox, Safari, Opera, Samsung Internet. Notable gap: Firefox for Android exposes recognition but not synthesis. Voices vary wildly by OS.
SpeechRecognition: Chrome/Edge/Opera (as webkitSpeechRecognition), and Safari 14.1+ on macOS / 14.5+ on iOS. Firefox: effectively no — there's an implementation behind an about:config flag, disabled by default, so treat it as unsupported.
Privacy — don't skip this. In Chrome, recognition streams your audio to a cloud service for processing; it is not on-device. Safari can do on-device recognition once the user grants permission and installs the language pack. For a notes app that might capture sensitive content, that's a real disclosure to make, not a footnote.
Both require a secure context, and recognition needs microphone permission.
Because Firefox lacks recognition, FieldKit feature-detects and hides the 🎤 button where it won't work, while keeping 🔊 read-aloud available wherever synthesis exists. Build for the split; don't assume both halves are present.
Verify on caniuse: SpeechRecognition and SpeechSynthesis before you rely on either.
How this compares to Electron
SpeechSynthesis works the same in Electron (it's Chromium) — same code, same platform voices. No advantage either way.
SpeechRecognition is where it gets interesting. Electron inherits Chromium's webkitSpeechRecognition, but historically it depended on Google's speech servers and an API key baked into the Chromium build — which is why many Electron apps find web-based recognition unreliable and instead bundle a native/offline engine (Vosk, Whisper, whisper.cpp, or an OS speech API) for private, offline transcription. So on desktop, the "serious" answer is often not the Web Speech API at all.
The PWA trades that control for zero setup and instant reach: on a supported browser, dictation is one API with no dependencies — and on Safari it can even be on-device. You give up the guaranteed-offline, fully-private transcription an Electron app can bundle.
The through-line: for text-to-speech, web and Electron are equivalent. For speech-to-text, the PWA is far easier to start with, while a desktop app that needs private, offline, or heavy-duty recognition will usually reach past the web API to a bundled engine.
Try it
Serve FieldKit over localhost (or HTTPS), tap 🎤 and start talking to compose a note hands-free, then tap 🔊 on any note to hear it read back. In Firefox you'll see only 🔊 — by design.
git clone https://github.com/JohnJunior/FieldKit.git
cd FieldKit
npx serve .
Read original: https://dev.to/alex_truhniy/talk-to-your-pwa-and-have-it-talk-back-speech-recognition-synthesis-fieldkit-companion-3dlm
← Previous
The State Snapshot: Your First Week on a New Project
Next →
An HTTP 200 is not a delivered job application
Related
An HTTP 200 is not a delivered job application
General
0
DEV Community 周榜
The State Snapshot: Your First Week on a New Project
General
3
DEV Community 周榜
Here’s How I Get the Most Out of My CLAUDE.md
General
1
DEV Community 周榜
How to Pay for a DeepSeek API Key When Your Country Blocks the Payment Rails
General
0
DEV Community 周榜
Comments0
No comments yet — be the first