Speech to Text HTML CSS JS — Web Speech API

Speech to Text · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Web Speech API: SpeechRecognition with a webkitSpeechRecognition fallback and feature detection
Continuous mode: recognition.continuous keeps listening through natural pauses
Interim results: provisional guesses shown live and replaced as the engine refines them
Final separation: isFinal and resultIndex split confirmed text from tentative text
Auto-restart: an onend loop guarded by an intent flag works around built-in timeouts
Error handling: not-allowed, no-speech, network and aborted codes mapped to clear messages
Multi-language: locale-aware selector that restarts recognition on change
Animated waveform: CSS keyframe bars indicate the mic is actively listening
Live counts: word and character totals computed from the final transcript
Graceful fallback: an unsupported notice and disabled controls where the API is missing

About this UI Snippet

How to Build Speech to Text with the Web Speech API in JavaScript

Screenshot of the Speech to Text snippet rendered live

Speech to text turns the microphone into a live transcriber: you talk, and words appear on screen in real time. This component is built entirely in the browser with the Web Speech API — specifically the SpeechRecognition interface — with no server, no cloud key and no library. It supports continuous dictation, shows tentative interim words before they are confirmed, switches between languages, and degrades gracefully where the API is unavailable. Here is exactly how it is constructed.

Accessing the recognition engine

The entry point is the SpeechRecognition constructor, which in Chromium-based browsers is exposed as webkitSpeechRecognition. The code resolves it with a fallback: const SR = window.SpeechRecognition || window.webkitSpeechRecognition. If neither exists — as in browsers without speech support — the script shows a friendly unsupported message and disables the controls instead of throwing. This feature-detection-first approach is essential because Web Speech support is uneven across browsers.

Once available, a recognition instance is created and configured with three key flags. recognition.continuous = true keeps it listening across pauses rather than stopping after the first phrase. recognition.interimResults = true makes the engine emit provisional, not-yet-final guesses as you speak. recognition.lang is set from the language selector so the acoustic and language models match the spoken language.

Separating interim and final results

The heart of the transcriber is the onresult event. Its event object carries a results list, where each entry has an isFinal flag and one or more alternatives with a transcript string. The handler loops from event.resultIndex to the end of the list and splits the output into two buckets: when isFinal is true the text is appended to a persistent finalTranscript string, and when it is false the text is collected into a temporary interim string for that frame.

This separation is what produces the familiar live-typing feel. Final text is rendered solidly and never changes; interim text is shown in a lighter, italic style and is replaced on every event as the engine refines its guess. Because interim results are reissued continuously, the interim element is fully rewritten each time rather than appended, while the final element only ever grows.

Keeping the session alive

Browsers automatically stop recognition after a period of silence and fire the onend event. To provide truly continuous dictation, the handler restarts recognition whenever it ends but the user has not pressed stop. A listening flag tracks intent: if the user still wants to listen, onend calls recognition.start() again; if they pressed stop, it simply updates the UI. This restart loop works around the engine's built-in timeouts so long-form dictation does not silently die.

Handling errors

The onerror event reports a coded error string, and the component handles the common ones explicitly. not-allowed and service-not-allowed mean the user blocked microphone permission, so it shows a permission prompt and stops trying. no-speech fires when nothing was heard; it is non-fatal and recognition can continue or restart. network indicates the speech service could not be reached. aborted occurs on a deliberate stop. Mapping these codes to clear messages prevents the confusing silent failures that plague naive implementations.

Language selection

A select element is populated with several language-locale codes (for example en-US, es-ES, fr-FR, de-DE, hi-IN). Changing it updates recognition.lang; if recognition is currently running it is restarted so the new language model takes effect immediately. Locale codes matter — en-US and en-GB bias toward different vocabularies and accents — so exposing them gives noticeably better accuracy than a bare language name.

The waveform and UI feedback

While listening, a small CSS keyframe animation drives three bars that scale vertically out of phase, mimicking a sound-level meter. The animation is purely cosmetic — it is toggled with a class when listening starts and stops — but it reassures the user that the mic is active. A live word and character count is computed from the combined final transcript, and Copy and Clear buttons let the user grab or reset the text. Copy uses navigator.clipboard.writeText with a brief confirmation label.

Why it is robust

The combination of feature detection, the interim-versus-final split keyed on resultIndex and isFinal, an onend restart loop guarded by an intent flag, and explicit error-code handling is what separates a toy demo from a usable transcriber. Everything runs locally in the browser using only the Web Speech API, so there is nothing to deploy and no audio leaves the device except to the browser's own speech service.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to trace the restart-loop and interim/final split by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why onend calls recognition.start() again only when a listening flag is still true, and how the resultIndex-to-results.length loop separates isFinal text from interim text on every single onresult event. The same assistant can help optimize it, for instance checking whether restarting recognition immediately inside onend could ever fire in a tight loop if the browser rejects start() repeatedly (e.g. after a permission revocation), and whether that needs a backoff or attempt limit. It is just as useful for extending the transcriber: ask it to add basic punctuation insertion from pause detection, persist the transcript to localStorage so a page refresh doesn't lose it, or add a voice-command mode that parses specific phrases out of the final transcript to trigger UI actions. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a continuous "speech to text" transcriber in plain HTML, CSS, and JavaScript using the Web Speech API's SpeechRecognition interface — no server, no external library.

Requirements:
- Resolve the recognition constructor with a fallback: window.SpeechRecognition or window.webkitSpeechRecognition, and if neither exists, show a distinct "unsupported" message and disable the start control instead of throwing an error anywhere else in the code.
- Configure the recognition instance with continuous set to true and interimResults set to true, and a language attribute driven by a select dropdown of locale codes (not just bare language names).
- In the result handler, loop from the event's resultIndex to the end of its results list, and for each result check its isFinal flag: append final results permanently to a persistent finalTranscript string, and collect non-final results into a temporary interim string that gets fully replaced (not appended) on every event, since interim guesses are revised continuously as the engine refines them.
- Render final text and interim text in visually distinct styles (e.g. solid color for final, lighter italic for interim) in two separate inline elements so the difference is visually obvious.
- Implement continuous dictation across the engine's built-in silence timeouts: track a boolean flag reflecting the user's actual intent to keep listening, and in the recognition end event, restart recognition automatically if that intent flag is still true, or finalize the UI state if the user explicitly pressed stop.
- Handle at least three distinct error codes from the error event (permission denied, no speech detected, network error) with distinct human-readable messages rather than one generic error string.
- Add live word and character counts computed from the final transcript, plus copy-to-clipboard and clear controls.

Step by step

How to Use

  1. 1
    Choose a languageSelect your spoken language and locale from the dropdown before you start.
  2. 2
    Start listeningClick start and grant microphone permission when the browser asks.
  3. 3
    Speak naturallyTalk at a normal pace; tentative words appear in light text and firm up as final.
  4. 4
    Watch it continueKeep talking through pauses — the session auto-restarts so dictation does not stop.
  5. 5
    Copy the transcriptClick Copy to place the full finalized text on your clipboard.
  6. 6
    Clear and restartUse Clear to reset the transcript and begin a fresh session.

Real-world uses

Common Use Cases

Voice form filling
Let users dictate into text fields hands-free, complementing inputs like a pattern lock for secure flows.
Note-taking apps
Capture spoken notes, meeting snippets or journal entries directly in the browser.
Language practice
Let learners speak and see their pronunciation transcribed across multiple locales.
Voice commands
Parse the transcript to trigger navigation or actions in an accessible interface.
Accessibility features
Offer speech input as an alternative to typing for users with motor or vision needs.
Web Speech reference
A working example of continuous recognition and interim results alongside a color wheel picker.

Got questions?

Frequently Asked Questions

Speech recognition works in Chromium-based browsers (Chrome, Edge) via webkitSpeechRecognition. Firefox and some others lack it, which is why the code feature-detects and shows an unsupported fallback rather than failing.

The engine automatically ends after a stretch of silence and fires onend. The component restarts recognition in that handler, guarded by a listening flag, so dictation continues until the user explicitly stops.

Interim results are the engine best current guess and change as you keep speaking, shown in light text. Final results carry isFinal true, are locked in, and are appended permanently to the transcript.

The component itself has no backend, but the browser may send audio to its own speech service to perform recognition. No audio is sent to any server you control, and you can review your browser privacy settings for details.

Pick the most specific locale, such as en-GB versus en-US, since each biases the language model toward different vocabulary and pronunciation. Speaking clearly at a steady pace and using a good microphone also helps significantly.

Yes. Use the JSX, Vue, Angular, or Tailwind export buttons. In React, create the SpeechRecognition instance once in a ref, attach result handlers in useEffect, and stop recognition in the cleanup return. The Web Speech API itself works identically in any framework.