A free, private teleprompter that follows your voice — running entirely in your browser.
LocalPrompt is a browser-based teleprompter for YouTubers, streamers, course creators, and anyone who presents to a camera. It listens as you speak and scrolls automatically to keep your place, with no foot pedal and no guessing at a fixed speed. Your script, settings, and recordings never leave your device — there are no accounts, no uploads, and no servers touching your content.
🔗 Live app: local-prompt.vercel.app
- Voice tracking — Uses the browser's Web Speech API to transcribe what you say in real time and a custom word-matching algorithm to align it to your script. Pause, ad-lib, or skip ahead, and the prompter follows and re-syncs.
- Three scroll modes:
- Voice — scrolls as you speak (default).
- Auto — steady scroll at an adjustable speed (10–300 px/s), tunable live with arrow keys.
- Manual — you control scrolling with the keyboard or trackpad.
- Audio recording — Capture your take while reading via
MediaRecorder, then play back, download (.webm/.ogg/.m4a), or delete. Recordings are reachable from both the editor and the prompter control bar. - Live settings preview — A scaled, pixel-faithful miniature of the prompter shows exactly how your script will look before you press Start, including the reading guide aligned to the active line.
- Deep customization — Text size, line spacing, side margins, alignment, four color themes (Classic, Amber, Paper, Night Blue), a movable reading guide, and horizontal/vertical mirroring for beam-splitter rigs.
- 100% local-first — Script and settings persist only in
localStorage; recordings live in memory as object URLs until you download them. Nothing is sent anywhere. - Fullscreen, keyboard-driven prompting — Space to play/pause, arrows for speed/scroll,
Rto restart,Escto exit.
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript |
| UI | React 19, Base UI primitives (shadcn-style wrappers) |
| Styling | Tailwind CSS v4 (CSS-first config, design tokens in globals.css) |
| Icons | lucide-react |
| Speech | Web Speech API (SpeechRecognition / webkitSpeechRecognition) |
| Audio | MediaRecorder + getUserMedia |
| Analytics | @vercel/analytics |
| Image tooling | sharp (favicon / OG generation at build time) |
There is no backend and no database — the app is entirely client-side. Everything renders from a single route.
app/
layout.tsx Root layout, fonts, full SEO metadata + JSON-LD
page.tsx Single route — renders the teleprompter app
globals.css Tailwind v4 theme tokens, dark theme, custom scrollbars
robots.ts Generated robots.txt
sitemap.ts Generated sitemap.xml
components/teleprompter/
teleprompter-app.tsx Top-level state container & mode switching
settings-panel.tsx Editor sidebar controls (sliders, toggles, themes)
settings-preview.tsx Scaled live preview of the prompter view
prompter-view.tsx Fullscreen scrolling reader + control bar
recordings-list.tsx Playback / download / delete UI for takes
components/ui/ Base UI primitive wrappers (button, slider, sheet, …)
hooks/
use-speech-recognition.ts Web Speech API session lifecycle + tracking
use-recorder.ts MediaRecorder lifecycle + recordings state
lib/
prompter.ts Types, themes, defaults, tokenizer, matching algorithm
utils.ts cn() class helper
teleprompter-app.tsx is the single source of truth. It owns:
mode—'edit'(script editor + settings) or'prompt'(fullscreen reader).scriptandsettings— debounced intolocalStorageunder a versioned key so a session restores on reload.currentWordIndex— the active reading position, shared between the speech hook and the rendered view.- The
useRecorderanduseSpeechRecognitionhook instances.
Settings are typed by PrompterSettings in lib/prompter.ts, which also defines the theme palette (PROMPTER_THEMES) and DEFAULT_SETTINGS.
The hardest part of the app is keeping the reading position aligned to a noisy, real-time speech transcript. This lives in lib/prompter.ts and hooks/use-speech-recognition.ts.
- Tokenization (
tokenizeScript) splits the script into tokens, preserving paragraph breaks and assigning awordIndexto each speakable word.normalizeWordlowercases, strips diacritics and punctuation so comparisons are robust. - Fuzzy word matching (
wordsMatch) accepts a spoken word as a match when it is identical, shares a prefix on longer words (scroll↔scrolling), or differs by a single edit (capped edit-distance). This absorbs transcription quirks. - Position advancement (
advanceMatch) walks spoken words against a lookahead window of the script:- Exact match at the current position is always accepted.
- Small forward jumps are accepted for content words, or for stopwords only when the next spoken word confirms the next script word (bigram confirmation).
- Larger jumps within the window require bigram confirmation, preventing false jumps on common words.
- After several consecutive misses, a wider re-sync search uses the last two missed words as a bigram to recover when the reader skips ahead or improvises.
- Commit vs. interim (in the hook): position is committed only from finalized transcript segments (which never change), while interim results drive a live preview that is re-evaluated from the committed state each time. Display position is kept monotonic so engine corrections never cause the text to jump backward.
- Session resilience: browser speech engines stop after silence, so the hook transparently restarts recognition while active, and tears down handlers with
abort()on stop so a dying session can never restart a dead one.
use-recorder.ts wraps getUserMedia + MediaRecorder, preferring audio/webm;codecs=opus where supported and falling back gracefully. Each take is stored as a Blob plus an object URL with duration and timestamp. Downloads create a fresh object URL from the blob and use a DOM-attached anchor so they work across browsers (including Firefox). Status surfaces unsupported and denied states so the UI can explain what happened.
- Script & settings:
localStorageonly, on the user's device. - Recordings: in-memory
Blobs / object URLs, never persisted or uploaded; cleared on refresh unless downloaded. - Speech: handled by the browser's built-in engine. Note that some browsers (e.g. Chrome) may process audio via the vendor's cloud service as part of the Web Speech API — for fully offline use, choose auto or manual scroll mode. This caveat is surfaced in the app footer.
# install dependencies
pnpm install
# start the dev server
pnpm devOpen http://localhost:3000.
Voice mode requires a microphone and a browser that supports the Web Speech API (Chrome, Edge, and Safari work best). Auto and manual modes work everywhere.
pnpm build # production build
pnpm start # serve the production build
pnpm lint # lint| Feature | Support |
|---|---|
| Auto / manual scroll, recording, all settings | All modern browsers |
| Voice tracking | Chrome, Edge, Safari (browsers exposing SpeechRecognition) |
When voice tracking isn't available the app detects it and prompts you to use auto or manual mode instead.
This repository is linked to a v0 project — start new chats to make changes and v0 will push commits directly to this repo. Every merge to main automatically deploys.
Built with Anthropic Claude and v0 by Vercel, by Jordan Harrison.