Examples # Examples > Learn by example with working demos and code samples Explore working examples that demonstrate ResponsiveVoice capabilities. All examples live in the [examples repository](https://github.com/responsivevoice/examples). ## CDN Examples Drop a ` ``` Without an API key the library runs in **demo mode** — it falls back to browser-native Web Speech API voices. Call `rv.isDemoMode()` at runtime to detect this state (for example, to surface a “Demo Mode” indicator in your UI). ## Next Steps * [Extended Usage Example](/examples/extended) — voice selection, speech controls, event logging * [npm + Vite Example](/examples/vite) — the same flow via ESM import and a bundler * [Events Guide](/guides/events) — the full event lifecycle * [Voice Selection Guide](/guides/voice-selection) — picking voices programmatically # CLI Tool > Command-line text-to-speech utility for Node.js A command-line tool that synthesizes text to speech and saves audio to a file. Uses `@responsivevoice/api-client` directly — server-side only, no browser involvement. Tip [Source code](https://github.com/responsivevoice/examples/tree/main/node/cli) ## Quick start ```bash export RESPONSIVEVOICE_API_KEY="your-api-key" # https://app.responsivevoice.org export RESPONSIVEVOICE_API_SECRET="your-api-secret" # "Server-to-server API secrets" npm install npm run cli -- "Hello, world!" --output hello.mp3 ``` Unlike the browser examples, the CLI has no demo mode — the API client requires a valid key and secret to make HTTP requests to the synthesis endpoint. The `README` in the source tree lists every flag (`--voice`, `--rate`, `--pitch`, `--volume`, `--stdout`, `--list-voices`). ## Next Steps * [HTTP Server](/examples/server) — REST wrapper around the same API client * [REST API](/rest-api/) — direct HTTP usage * [API Client Reference](/api/api-client/src) — full client documentation # Events & Callbacks Example > Live view of every public event and per-call callback firing A focused teacher for the speech lifecycle — speak one utterance and watch every public event and per-call callback fire in real time. Tip [Live demo](https://examples.responsivevoice.org/browser/events-callbacks/) · [Source code](https://github.com/responsivevoice/examples/tree/main/browser/events-callbacks) ## Why a separate example There are two parallel ways to observe speech: `responsiveVoice.addEventListener(name, fn)` (process-wide, persists across calls) and inline `onstart` / `onend` / `onerror` / `onboundary` callbacks passed to `speak()` (bound to a single utterance). Most confusion in the wild stems from mixing them up. This example surfaces both at the same visual level — a row of per-call pills bound to one utterance, and a row of global pills that fire across the lifetime of the page — so the distinction is concrete instead of conceptual. ## What you’ll see * **Two rows of pills.** The top row (four pills: `onstart`, `onboundary`, `onend`, `onerror`) are the per-call callbacks you pass to `speak()`. The bottom row (nine pills: `OnReady`, `OnVoiceResolved`, `OnStart`, `OnPartStart`, `OnPartEnd`, `OnPause`, `OnResume`, `OnEnd`, `OnError`) are the global events you register with `addEventListener()`. * **The default text triggers chunking.** The text box starts with a multi-sentence sample long enough that the engine splits it into several parts. Click Speak and you’ll see `OnPartStart` show `part X of N` and `OnPartEnd` count up as each chunk finishes. * **Pills reset on every Speak click**, except `OnReady` — that one fires once when the page loads and stays green for the rest of the session. * **The full event log sits below the pills.** Once you recognize a pill, you can scroll the log to see the actual data each event carries. * **`onboundary` only fires for browser-native voices.** When a voice plays through the HTTP fallback (server-side audio), the boundary callback stays quiet and its counter doesn’t move. * **`OnPause` has a \~60-second browser limit.** If you click Pause and don’t click Resume within about a minute, the browser cancels the speech automatically and you’ll see `OnEnd` fire instead of `OnResume`. ## Next Steps * [Events Guide](/guides/events) — full event reference and async/await patterns * [Basic Example](/examples/basic) — minimal `OnReady` + `speak()` flow without the inspection scaffolding # Extended Usage Example > Full-featured demo with voice selection, speech controls, and event logging A full-featured example showing voice selection, playback controls, speech parameter tuning, and a live event log. Tip [Live demo](https://examples.responsivevoice.org/browser/extended/) · [Source code](https://github.com/responsivevoice/examples/tree/main/browser/extended) ## What it covers * Voice browser with language filtering * `speak` / `pause` / `resume` / `cancel` controls * Rate / pitch / volume sliders * Platform detection (browser, OS, device type) * Real-time event log * Demo mode indicator driven by `rv.isDemoMode()` * Force-fallback toggle (use HTTP audio instead of Web Speech API) ## Speech parameter ranges | Parameter | Min | Default | Max | Description | | --------- | --- | ------- | --- | ------------ | | `rate` | 0.1 | 1.0 | 2.0 | Speech speed | | `pitch` | 0.1 | 1.0 | 2.0 | Voice pitch | | `volume` | 0.0 | 1.0 | 1.0 | Audio volume | ```js rv.speak('Hello!', 'UK English Female', { rate: 1.2, pitch: 1.0, volume: 0.8, }); ``` ## Force fallback mode Bypass the Web Speech API and always use HTTP audio from the server: ```js await rv.init({ apiKey: 'your-api-key', forceFallback: true }); ``` Useful when you need consistent audio across browsers, or when Web Speech API voices are unavailable on the target device. ## Next Steps * [Basic Example](/examples/basic) — the minimal starting point * [Voice Selection Guide](/guides/voice-selection) — advanced voice filtering * [Events Guide](/guides/events) — full event reference # HTTP Server > REST API server for text-to-speech synthesis A minimal HTTP server that exposes REST endpoints for text-to-speech synthesis. Useful as a reverse proxy — keeps your API key off the browser and lets your frontend call your own origin. Tip [Source code](https://github.com/responsivevoice/examples/tree/main/node/server) ## Quick start ```bash export RESPONSIVEVOICE_API_KEY="your-api-key" # https://app.responsivevoice.org export RESPONSIVEVOICE_API_SECRET="your-api-secret" # "Server-to-server API secrets" npm install npm run server # Server on http://localhost:3001 ``` ## Endpoints | Method | Path | Description | | ------ | ---------------------- | ----------------------------- | | GET | `/` | API documentation | | GET | `/voices` | List all voices | | GET | `/voices/:lang` | Voices by language | | POST | `/synthesize` | Synthesize speech (JSON body) | | GET | `/synthesize?text=...` | Synthesize via query params | ## Calling from a frontend ```js async function speak(text) { const response = await fetch('http://localhost:3001/synthesize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, voice: 'UK English Female' }), }); const audio = new Audio(URL.createObjectURL(await response.blob())); audio.play(); } ``` The server ships with permissive CORS headers (`Access-Control-Allow-Origin: *`). Tighten to your domain(s) before deploying anything public-facing. ## Next Steps * [CLI Tool](/examples/cli) — same API client, command-line form * [REST API](/rest-api/) — direct HTTP usage * [API Client Reference](/api/api-client/src) — full client documentation # npm + Vite Example > Installed from npm, bundled for the browser by Vite `@responsivevoice/core` installed from npm and bundled for the browser by [Vite](https://vitejs.dev/). Same feature set as the [Basic Example](/examples/basic), but the library arrives via ESM `import` instead of a CDN ` ``` The feature ships in the CDN bundle — no extra imports. ## Multi-mount When `selector` matches more than one top-level element, an independent player is mounted on each. Single-active-narrator coordination is implicit: starting playback on one resets every other player to idle. Nested matches (e.g. `
` inside `
`) are filtered automatically — only the outermost match mounts. Publishers who specifically want nested mounts write a more specific selector. ## Imperative mount For SPAs and lazy-loaded sections, call `rv.webPlayer?.mount(selectorOrElement, overrides?)` after the element is in the DOM: ```js fetch('/article/123') .then((r) => r.text()) .then((html) => { document.getElementById('dynamic-area').innerHTML = html; const handle = rv.webPlayer?.mount('#dynamic-area', { theme: 'neutral' }); // later, before removing the element from the DOM: // handle?.unmount(); }); ``` Overrides are leaf-merged over the init config — `{ controls: { brand: false } }` keeps the rest of the init defaults intact. ## Skipping content Any element carrying `data-rv-skip` is excluded from narration: ```html

Read this paragraph.

This block is skipped.

Continue reading from here.

``` ## Next steps * [Web Player Customization](/examples/web-player-customization) — live control panel for every option. # Web Player Customization > Live control panel for every webPlayer option — theme, controls, navigation, layout, and CSS spacing variable. Tune every `webPlayer` option in real time. Each toggle re-mounts the player via `rv.webPlayer.mount()` with leaf-merged overrides, and the matching JSON config snippet updates alongside the player so you can copy-paste the result into your own setup. Tip [Live demo](https://examples.responsivevoice.org/browser/web-player-customization/) · [Source code](https://github.com/responsivevoice/examples/tree/main/browser/web-player-customization) ## What you can tweak * **Theme** — `Neutral`, `ResponsiveVoice`, or custom tokens (9-token color picker covering bg, fg, muted, accent, accentSoft, hover, border, track, fill). * **Controls** — toggle progress, time, skip, speed, brand individually. Play / pause is always shown. * **Navigation** — paragraph highlight and click-to-jump independently. * **Position** — main-pill placement: keyword (`before` / `after` / `inline`) relative to the container, or `{ target, at }` for a custom mount target. * **Layout** — main-pill width (`shrink` / `fill`) and outer display (`block` / `inline`). * **Mini-Player** — visibility, viewport corner (or CSS-offset object), and entrance/exit animation. * **Sanitize** — keep scripts, styles, controls, and media out of narration (on by default), plus extra `exclude` selectors of your own. * **Spacing** — the `--rv-player-margin` CSS custom property, controlling the player’s surrounding margin. ## How re-mount works The example panel keeps a single mount handle and replaces it on every change: ```js let handle = null; function remount() { handle?.unmount(); handle = rv.webPlayer?.mount('#sample', buildConfig()); } ``` `buildConfig()` reads the panel state and returns a `WebPlayerMountOverrides` object that’s leaf-merged over the init config. The same shape works in `rv.init({ features: { webPlayer: ... } })` for static sites. ## Custom mount target The `position` field accepts either a keyword (`'before'`, `'after'`, `'inline'`) relative to `selector`, or an object that mounts the player into any element on the page. This is useful when the article element is constrained by your layout (sidebar, fixed slot, CMS-driven composition): ```ts rv.webPlayer?.mount('#article', { position: { target: '#player-slot', at: 'inside' }, }); ``` `at` accepts `'inside'` (first child of the target, the default), `'before'` (sibling before), or `'after'` (sibling after). `target` is required — the keyword form covers the article-relative case. If `target` doesn’t match anything in the DOM at mount time, the player falls back to the keyword `'before'` and logs a warning. ## Mini-player position The floating mini-player surfaces when the main player scrolls out of view. Its viewport corner is configurable via `miniPlayer.position`: ```ts rv.webPlayer?.mount('#article', { miniPlayer: { enabled: true, position: 'bottom-right' }, }); ``` Accepted values: * A corner keyword: `'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`. Default `'bottom-left'`. * A CSS-offset object: `{ top, right, bottom, left }`, each a CSS length string. At least one side is required; opposing sides (`top` + `bottom`, `left` + `right`) are rejected. ```ts rv.webPlayer?.mount('#article', { miniPlayer: { enabled: true, position: { top: '80px', right: '20px' } }, }); ``` The boolean shorthand is the natural form for the on/off case — `miniPlayer: true` enables the mini-player at the default corner, `miniPlayer: false` disables it. ## Mini-player animation `miniPlayer.animation` controls how the mini-player enters and leaves as you scroll: ```ts rv.webPlayer?.mount('#article', { miniPlayer: { enabled: true, animation: 'fade' }, }); ``` Accepted values: * `'slide'` — fades in and slides from the docked corner. Default. * `'fade'` — fades in and out, no movement. * `'pop'` — fades in with a brief scale-up. * `'none'` — appears and disappears instantly. The slide direction follows where the mini-player sits: anything in the lower half of the viewport rises into place, anything in the upper half drops in — so corner keywords and custom offsets both enter from the nearest edge. Motion is skipped automatically when the browser requests reduced motion, so every preset falls back to an instant swap for those readers. ## Choosing a voice per player By default every web player narrates with the website’s default voice (the `voice` profile from `/v2/config`). The web-player config exposes four flat playback fields — `voice`, `rate`, `pitch`, `volume` — that mirror the arguments of `core.speak(text, voice, params)`. Each is independently optional and overrides the website default for one player: ```ts rv.webPlayer?.mount('#article', { voice: 'US English Male', rate: 0.9, }); ``` `voice` accepts the full [`VoiceSelector`](/guides/voice-selection/) grammar. In JS, write whichever form is most natural — strings for named voices, real `RegExp` literals for patterns, plain objects for structured queries. The schema normalizes a `RegExp` to its JSON-clean `{ regex, flags }` form on parse, so the same selector works identically in JS, server config, and every SDK language: ```ts // 1. Exact voice name rv.webPlayer?.mount('#en-article', { voice: 'UK English Female', }); // 2. Structured query — pick a Portuguese female voice from any provider rv.webPlayer?.mount('#pt-article', { voice: { lang: 'pt', gender: 'female' }, }); // 3. Regex pattern — first voice whose name matches rv.webPlayer?.mount('#multi-article', { voice: /English.*Male/i, }); ``` The shape parallels `speak()` directly: ```ts // Direct speak() call: rv.speak('Hello', /UK English/, { rate: 0.9, volume: 0.8 }); // ^text ^voice ^speech params // Equivalent web-player config — same fields, same names: rv.webPlayer?.mount('#article', { voice: /UK English/, rate: 0.9, volume: 0.8, }); ``` ### Setting a default for every player The same fields are valid in the website config (`webPlayer.voice` / `rate` / `pitch` / `volume`), in which case every auto-discovered article starts with those values instead of the website-wide default: ```ts rv.init({ features: { webPlayer: { enabled: true, voice: 'US English Male', rate: 0.95, }, }, }); ``` Per-mount overrides leaf-merge over this config, so a player that mounts with `{ rate: 1.2 }` keeps the `'US English Male'` voice and only changes the rate. ### What inherits, what overrides Each playback field is independently optional. Any field you omit falls through to the website default voice profile (the profile’s `name` becomes the string-form `voice` selector when none is set): | Field | When omitted | | -------- | --------------------------------------------------------- | | `voice` | Inherits the website default voice’s `name` as a selector | | `pitch` | Inherits the website default `pitch` | | `rate` | Inherits the website default `rate` | | `volume` | Inherits the website default `volume` | ## Next steps * [Web Player Example](/examples/web-player) — the basic integration without the customization panel. * [Voice Selection](/guides/voice-selection/) — the full `VoiceSelector` grammar used by the `voice` field.