Speak Out Loud
built with: Web Speech API
Live speech-to-text for rehearsing answers, built on a browser API that TypeScript does not ship types for.
Typing an API the DOM lib forgot
`SpeechRecognition` is not in TypeScript's DOM library — it is still prefixed in most shipping browsers and never stabilised into the standard typings. Using it means either casting to `any` everywhere or writing the types once.
This app writes them once, in a shared module, covering only the surface actually used: the constructor, the three config flags, start/stop, and the three event handlers. Everything is named `*Like` as a reminder that these are hand-written approximations of a moving spec, not official types.
// src/lib/speech-recognition.ts
export interface SpeechRecognitionLike {
continuous: boolean;
interimResults: boolean;
lang: string;
start: () => void;
stop: () => void;
onresult: ((event: SpeechRecognitionEventLike) => void) | null;
onend: (() => void) | null;
onerror: ((event: SpeechRecognitionErrorEventLike) => void) | null;
}
export function getSpeechRecognition(): SpeechRecognitionConstructor | undefined {
const w = window as unknown as {
SpeechRecognition?: SpeechRecognitionConstructor;
webkitSpeechRecognition?: SpeechRecognitionConstructor;
};
return w.SpeechRecognition || w.webkitSpeechRecognition;
}Interim vs final results
With `interimResults` on, the API emits a running best guess that keeps getting revised, then marks a result `isFinal` once it settles. Rendering every event naively makes text flicker and duplicate.
The handler therefore reads from `event.resultIndex` forward and keeps final and interim text separate — finalised text is appended to the committed transcript, while the interim tail is rendered as provisional and replaced on the next event.
The same typed helper backs two features: Speak Up's rehearsal view and Mock Interview's live answer capture.
Important
Gotcha
Where it lives in the repo
- src/lib/speech-recognition.ts
- src/components/speak-up/speech-practice.tsx
- src/components/mock-interview/chat/use-speech-input.ts