text-reader

Text-to-speech reader with word-level highlighting. Uses the Web Speech API for synthesis and the CSS Custom Highlight API for word tracking.

Overview

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>

Attributes

AttributeTypeDefaultDescription
forstringID of the element to read. Required.
selectorsstringp,liComma-separated CSS selectors for text extraction within the target
speednumber1Initial playback rate (0.5–2)
voicestringvoiceURI to pre-select on load
highlightstringSet to "false" to disable word highlighting
scrollstringSet to "false" to disable auto-scroll to active word
label-playstringPlayAccessible label for the play button
label-pausestringPauseAccessible label for the pause button
label-stopstringStopAccessible label for the stop button

Observed Attributes

These attributes are reactive — changing them at runtime triggers an update:

AttributeBehavior on Change
speedRestarts speech from the current word at the new rate
voiceRestarts speech from the current word with the new voice
forStops current speech, re-resolves target element

JavaScript API

const reader = document.querySelector('text-reader'); reader.play(); // Begin or resume playback reader.pause(); // Pause mid-sentence reader.resume(); // Resume from pause reader.stop(); // Cancel and reset // Read-only properties reader.speaking; // boolean reader.paused; // boolean reader.progress; // 0–1 (charIndex / fullText.length) // Get available voices const voices = await reader.voices;
Method / PropertyTypeDescription
play()methodBegin playback (or resume if paused)
pause()methodPause mid-sentence
resume()methodResume from paused state
stop()methodCancel speech and reset position
voicesPromise<SpeechSynthesisVoice[]>Available TTS voices
speakingbooleanWhether speech is currently active
pausedbooleanWhether speech is paused
progressnumber (0–1)Current position as fraction of total text

Events

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'); });
EventDetailWhen
text-reader:play{ voice, speed }Speech starts
text-reader:pauseSpeech paused
text-reader:resumeSpeech resumed
text-reader:stopStopped 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

CSS Custom Properties

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); }
PropertyDefaultDescription
--text-reader-bgvar(--color-surface-raised)Control bar background
--text-reader-gap0.5remGap between controls
--text-reader-button-size2remButton width and height
--text-reader-highlightMarkWord highlight background color
--text-reader-highlight-textMarkTextWord highlight text color

Custom Icons

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.

Accessibility

  • Controls are wrapped in a role="group" with aria-label="Article reader"
  • Each button has an aria-label (customizable via label-* attributes)
  • All controls are keyboard-focusable and operable
  • Voice select and speed slider have accessible labels
  • Highlighting uses system colors (Mark / MarkText) by default, respecting user contrast preferences
  • Speech is cancelled on page unload to prevent orphaned audio

Browser Support

FeatureChromeFirefoxSafariNotes
Web Speech API33+49+7+Core feature
CSS Custom Highlight API105+117+17.2+Word highlighting only
Custom Elements67+63+10.1+Baseline
adoptedStyleSheets73+101+16.4+Highlight injection

Graceful Degradation

ConditionBehavior
No speechSynthesisElement hides itself (display: none)
No CSS.highlightsSpeech works normally, highlighting skipped
No target foundConsole warning, controls disabled