Web Player
The web player is a drop-in article reader with paragraph highlighting, click-to-jump, playback controls, and a floating mini-player that follows the reader once the main controls scroll out of view.
You can add a default web player to any page without code from the ResponsiveVoice Dashboard. This guide covers the code path — installing, configuring, and overriding the player programmatically with init() and mount().
For live examples, see the Web Player example and Web Player Customization pages.
At a Glance
Section titled “At a Glance”- Drop the CDN script in your page and call:
responsiveVoice.init({apiKey: 'YOUR_API_KEY',features: {webPlayer: { enabled: true },},});
- Choose a Theme:
neutral,responsivevoice, or custom color tokens. - Set the Layout if the player should appear somewhere other than before the article.
- Choose Controls such as progress, time, skip, speed, brand, and mini-player settings.
- Tune Behavior such as paragraph highlighting, click-to-jump, and skipping non-readable content.
- Use Advanced only when you need custom selectors, voice override, or extra narration exclusions.
Add the Script and Initialize
Section titled “Add the Script and Initialize”Drop the CDN bundle into your page and turn the player on. By default it attaches to the first <article> element it finds and narrates every p, h2, h3, and li inside it.
<script src="https://cdn.responsivevoice.org/sdk/latest/responsivevoice.js"></script><script> window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', // required — without it the player runs in demo mode (one voice) features: { webPlayer: { enabled: true }, }, });</script>If your page has an <article>, the player appears above it after initialization.
Theme optional
Section titled “Theme ”Theme controls the visual style of the player.
Two presets ship with the player, plus a custom-token path for brand colors. Tabs preserve your selection across pages.
The default theme is light, minimal, and designed to sit comfortably on most host pages. No extra configuration is needed:
window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true }, },});The ResponsiveVoice theme uses the same controls and layout, recolored around the ResponsiveVoice violet.
window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, theme: 'responsivevoice' }, },});Override individual color tokens to match your brand. Every field is optional; omitted values fall back to the neutral defaults.
window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, theme: { fill: '#10b981', track: '#d1fae5', accentSoft: '#d1fae5', }, }, },});The full nine-token palette is bg, fg, muted, accent, accentSoft, hover, border, track, and fill. The customization picker lets you tune those values and copy the matching configuration.
Layout optional
Section titled “Layout ”Layout controls where the player appears and how much space it uses.
| Config option | Default | What it does |
|---|---|---|
position | 'before' | Mounts the player before, after, inline inside, or inside a custom target. |
layout.mode | 'shrink' | shrink hugs the controls; fill spans the container. |
layout.display | 'block' | block gives the player its own line; inline flows with surrounding content. |
window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, position: 'before', layout: { mode: 'shrink', display: 'block', }, }, },});For a custom mount target, pass an object:
window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, position: { target: '#article-player', at: 'inside', }, }, },});Controls optional
Section titled “Controls ”Controls decide which buttons are visible and whether the floating mini-player is enabled.
| Config option | Default | What it does |
|---|---|---|
controls.progress | true | Shows progress through the readable content. |
controls.time | true | Shows elapsed and total estimated time. |
controls.skip | true | Shows previous and next paragraph buttons. |
controls.speed | true | Shows the speed cycle button. |
controls.brand | true | Shows the ResponsiveVoice brand icon. |
miniPlayer.enabled | true | Shows the mini-player when the main player is out of view. |
miniPlayer.position | 'bottom-left' | Places the mini-player in a viewport corner or custom offset. |
miniPlayer.animation | 'slide' | Uses none, fade, slide, or pop. |
window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, controls: { progress: true, time: true, skip: true, speed: true, brand: true, }, miniPlayer: { enabled: true, position: 'bottom-left', animation: 'slide', }, }, },});Behavior optional
Section titled “Behavior ”Behavior covers highlighting, click-to-jump, and skipping non-readable content.
| Config option | Default | What it does |
|---|---|---|
navigation.paragraphHighlight | true | Highlights the currently spoken element. |
navigation.paragraphClick | true | Lets visitors click a paragraph to start reading there. |
sanitize.enabled | true | Excludes scripts, styles, form controls, embedded media, and hidden content from narration. |
window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, navigation: { paragraphHighlight: true, paragraphClick: true, }, sanitize: { enabled: true }, }, },});Keep sanitize.enabled on for normal sites. Turning it off narrates raw matched text and is mainly useful for debugging unusual markup.
Advanced optional
Section titled “Advanced ”Advanced options are for site-specific markup and voice overrides.
Most websites can leave these at their defaults. Use them when the player needs to read a specific part of a page, skip extra elements, or use a different voice from the website default.
| Config option | Default | What it does |
|---|---|---|
selector | 'article' | CSS selector for the element that contains readable content. |
paragraphSelector | 'p, h2, h3, li' | CSS selector for readable elements inside the container. |
voice | Website default | Optional voice selector for this player only. |
sanitize.exclude | [] | Extra CSS selectors to skip in addition to the built-in exclusions. |
window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, selector: '.post-body', paragraphSelector: 'p, h2, h3, blockquote', voice: 'UK English Female', sanitize: { enabled: true, exclude: ['.pull-quote', '.byline'], }, }, },});If selector matches more than one element on the page, every match gets its own independent player. Starting playback on one automatically pauses every other player.
Nested matches, such as an <article> inside another <article>, are filtered to the outermost element so duplicate players are not created.
Place the Player in a specific container
Section titled “Place the Player in a specific container”Many WordPress themes and marketing sites wrap the entire page in an <article>. In that layout, the default selector: 'article' can place the player above the page title and narrate calls to action or other content that is not part of the listening experience.
Use a purpose-built content container and player slot instead:
<div id="article-player"><!-- The player will be placed here --></div><div id="article-preview"> <h2>A better way to experience the web</h2> <p>Visitors can listen while they commute, work, or rest their eyes.</p> <p>The current passage is highlighted as the article is read.</p></div>window.responsiveVoice.webPlayer?.mount('#article-preview', { voice: 'UK English Female', theme: 'responsivevoice', paragraphSelector: 'h2, p', position: { target: '#article-player', at: 'inside', },});This pattern is useful for product pages too: place a short, representative article demo after trust proof such as customer logos, then let visitors try the same player their readers would receive.
Voice Override
Section titled “Voice Override”Without configuration, the player narrates with the website default voice. To override per player, add a voice field. It accepts a name string, a RegExp, or a structured query, exactly like the second argument to speak().
window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, voice: 'UK English Female', rate: 0.95, }, },});The full grammar lives in Voice Selection. The same voice, rate, pitch, and volume fields are also valid as per-mount overrides on mount().
Exclude From Narration
Section titled “Exclude From Narration”Mark any element with data-rv-skip to keep it out of the narration. The player still highlights surrounding paragraphs; the skipped element is invisible to the reader's progression.
<article> <p>Read this paragraph.</p> <aside class="pullquote" data-rv-skip> Decorative pull-quote, not narrated. </aside> <p>Continue from here.</p></article>Useful for code blocks, figure captions, ad slots, pull quotes, and bylines.
By default the player never reads content that is not visible prose. Scripts, styles, form controls, embedded media, and elements marked hidden or aria-hidden="true" are excluded from narration even when they sit inside a matched paragraph. A paragraph left empty after sanitization is dropped from the reading flow.
Add your own selectors with sanitize.exclude. Those selectors are added to the built-in exclusions; they do not replace the built-in list.
Testing
Section titled “Testing”After integrating, verify on the real page — its markup determines what's found and narrated:
- Press play and confirm the selected voice sounds right.
- Confirm the configured controls (progress, time, skip, speed, brand) appear.
- Click a paragraph if
navigation.paragraphClickis enabled. - Confirm highlighting appears if
navigation.paragraphHighlightis enabled. - Scroll until the main player leaves view and confirm the mini-player appears if enabled.
- Add a known excluded element and confirm it is skipped.
Dynamic Content
Section titled “Dynamic Content”For SPAs and lazy-loaded sections, the article may not be in the DOM when init() runs, so the auto-discovery pass misses it. Use webPlayer.mount() to attach a player after the element is rendered:
const html = await fetch('/posts/123').then((r) => r.text());document.getElementById('post-area').innerHTML = html;
const handle = window.responsiveVoice.webPlayer?.mount('#post-area');
// later, before removing the element:handle?.unmount();mount() accepts the same option shape as the init config, leaf-merged over it. For example, mount('#post-area', { rate: 1.2 }) keeps every other setting intact and only changes the rate.
It returns null if the feature is not enabled or the element cannot be found, so the ?. guards are intentional.
Troubleshooting
Section titled “Troubleshooting”The player doesn't appear
Section titled “The player doesn't appear”
The player doesn't appear
Section titled “The player doesn't appear”- Confirm
init()ran and resolved, andfeatures.webPlayer.enabledistrue. - Confirm
selectormatches an element that is already in the DOM wheninit()runs. Content rendered later (SPA, lazy-load) is missed by the one-time auto-discovery pass — mount it explicitly withmount().
mount() returned null
Section titled “mount() returned null”
mount() returned null
Section titled “mount() returned null”It returns null when the feature isn't enabled or the target element isn't in the DOM — which is why the ?. guards matter. Confirm enabled: true and that the element exists before the call.
The wrong content is read, or paragraphs are missing
Section titled “The wrong content is read, or paragraphs are missing”
The wrong content is read, or paragraphs are missing
Section titled “The wrong content is read, or paragraphs are missing”paragraphSelectormust match the elements you want narrated (defaultp, h2, h3, li).- Sanitization drops scripts, styles, form controls, media, and
hidden/aria-hiddennodes, plus anything matchingdata-rv-skipor yoursanitize.excludeselectors; a paragraph left empty after sanitization is skipped.
The mini-player doesn't appear
Section titled “The mini-player doesn't appear”
The mini-player doesn't appear
Section titled “The mini-player doesn't appear”miniPlayer.enabled must be true, and it only surfaces while playback is active and the main player has scrolled out of view.
Handlers leak across SPA navigations
Section titled “Handlers leak across SPA navigations”
Handlers leak across SPA navigations
Section titled “Handlers leak across SPA navigations”Call handle.unmount() before removing the host element — the player keeps listeners on the article and document until you do.