Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Text-to-speech reader with word-level highlighting. Uses the Web Speech API for synthesis and the CSS Custom Highlight API for word tracking.
The <text-reader> component attaches a text-to-speech control bar to any element. It uses the Web Speech API for synthesis and the CSS Custom Highlight API for real-time word tracking. Progressive enhancement — the component hides itself if the browser lacks speechSynthesis.
<text-reader for="my-article" speed="1"></text-reader> <article id="my-article"> <p>The web platform has evolved dramatically over the past decade. Modern browsers now support features that previously required complex JavaScript libraries or plugins.</p> <p>Progressive enhancement remains the most resilient approach to building for the web.</p></article>
| Attribute | Type | Default | Description |
|---|---|---|---|
for | string | — | ID of the element to read. Required. |
variant | string | — | Set to "icon" for a compact speaker icon with floating mini-toolbar |
selectors | string | p,li | Comma-separated CSS selectors for text extraction within the target |
speed | number | 1 | Initial playback rate (0.5–2) |
voice | string | — | voiceURI to pre-select on load |
highlight | string | — | Set to "false" to disable word highlighting |
scroll | string | — | Set to "false" to disable auto-scroll to active word |
label-play | string | Play | Accessible label for the play button |
label-pause | string | Pause | Accessible label for the pause button |
label-stop | string | Stop | Accessible label for the stop button |
These attributes are reactive — changing them at runtime triggers an update:
| Attribute | Behavior on Change |
|---|---|
speed | Restarts speech from the current word at the new rate |
voice | Restarts speech from the current word with the new voice |
for | Stops current speech, re-resolves target element |
const reader = document.querySelector('text-reader'); reader.play(); // Begin or resume playbackreader.pause(); // Pause mid-sentencereader.resume(); // Resume from pausereader.stop(); // Cancel and reset // Read-only propertiesreader.speaking; // booleanreader.paused; // booleanreader.progress; // 0–1 (charIndex / fullText.length) // Get available voicesconst voices = await reader.voices;
| Method / Property | Type | Description |
|---|---|---|
play() | method | Begin playback (or resume if paused) |
pause() | method | Pause mid-sentence |
resume() | method | Resume from paused state |
stop() | method | Cancel speech and reset position |
voices | Promise<SpeechSynthesisVoice[]> | Available TTS voices |
speaking | boolean | Whether speech is currently active |
paused | boolean | Whether speech is paused |
progress | number (0–1) | Current position as fraction of total text |
const reader = document.querySelector('text-reader'); reader.addEventListener('text-reader:play', e => { console.log('Playing at speed:', e.detail.speed);}); reader.addEventListener('text-reader:word', e => { console.log('Word:', e.detail.word);}); reader.addEventListener('text-reader:end', e => { console.log('Finished in', e.detail.duration, 'ms');});
| Event | Detail | When |
|---|---|---|
text-reader:play | { voice, speed } | Speech starts |
text-reader:pause | — | Speech paused |
text-reader:resume | — | Speech resumed |
text-reader:stop | — | Stopped or cancelled |
text-reader:end | { duration } | Reached end of text |
text-reader:word | { word, charIndex, element } | Each word boundary |
text-reader:error | { error } | SpeechSynthesis error |
text-reader { --text-reader-bg: var(--color-surface-raised); --text-reader-gap: 0.75rem; --text-reader-button-size: 2.5rem; --text-reader-highlight: oklch(85% 0.15 90); --text-reader-highlight-text: oklch(20% 0 0);}
| Property | Default | Description |
|---|---|---|
--text-reader-bg | var(--color-surface-raised) | Control bar background |
--text-reader-gap | 0.5rem | Gap between controls |
--text-reader-button-size | 2rem | Button width and height |
--text-reader-highlight | Mark | Word highlight background color |
--text-reader-highlight-text | MarkText | Word highlight text color |
Replace the default text symbols with custom icons using slot attributes:
<text-reader for="my-article"> <icon-wc slot="icon-play" name="play" aria-hidden="true"></icon-wc> <icon-wc slot="icon-pause" name="pause" aria-hidden="true"></icon-wc> <icon-wc slot="icon-stop" name="square" aria-hidden="true"></icon-wc></text-reader>
Any element with slot="icon-play", slot="icon-pause", or slot="icon-stop" will be moved into the corresponding button.
Use variant="icon" for a compact speaker icon that opens a floating mini-toolbar on click. Ideal for article toolbars and <page-tools> integration.
<nav class="article-toolbar" aria-label="Article tools"> <share-wc variant="icon"></share-wc> <print-page></print-page> <text-reader for="my-article" variant="icon"></text-reader></nav> <article id="my-article"> <p>Content to read aloud…</p></article>
The floating toolbar provides play/pause, stop, and speed controls. It appears above the trigger icon and auto-dismisses when reading ends. Click outside the toolbar to hide it without stopping playback; click the trigger again to re-show it.
Customize the trigger icon with slot="icon-trigger":
<text-reader for="my-article" variant="icon"> <icon-wc slot="icon-trigger" name="headphones" aria-hidden="true"></icon-wc></text-reader>
role="group" with aria-label="Article reader"aria-label (customizable via label-* attributes)Mark / MarkText) by default, respecting user contrast preferences| Feature | Chrome | Firefox | Safari | Notes |
|---|---|---|---|---|
| Web Speech API | 33+ | 49+ | 7+ | Core feature |
| CSS Custom Highlight API | 105+ | 117+ | 17.2+ | Word highlighting only |
| Custom Elements | 67+ | 63+ | 10.1+ | Baseline |
adoptedStyleSheets | 73+ | 101+ | 16.4+ | Highlight injection |
| Condition | Behavior |
|---|---|
No speechSynthesis | Element hides itself (display: none) |
No CSS.highlights | Speech works normally, highlighting skipped |
| No target found | Console warning, controls disabled |