An accessible read-along article reader as a single web component. It reads articles aloud in a natural, fully on-device voice and highlights the current sentence and word as it speaks, on a calm dark (or high-contrast) background.
Built for people with low vision, dyslexia, or ADHD β and anyone who finds it easier to follow along when they can both see and hear the text.
Status: Early stages. The API may change before
1.0.
Most "listen to this page" tools use robotic voices, light backgrounds, and give
you no visual anchor for where you are. readalong-reader is the opposite:
- Natural voice, no cloud, no cost. The optional Kokoro engine generates speech locally in the browser. Nothing is sent to an API and there are no per-character fees.
- A place for your eyes. The current sentence is softly highlighted and the current word brightly highlighted, with optional focus dimming that fades everything else back to cut visual clutter.
- Comfortable by default. Dark, sepia, and high-contrast themes; adjustable text size, line spacing, and speed; keyboard control throughout.
- ποΈ Two voice engines, swappable at runtime
systemβ the browser's built-inspeechSynthesis. Zero dependencies and exact per-word highlighting. Works everywhere, including Safari.kokoroβ a natural 82M-parameter neural voice running on-device throughkokoro-js(WebGPU, WASM fallback). Optional peer dependency β only loaded if you use it.
- π¦ Synchronized sentence + word highlighting and smooth auto-scroll.
- πΌοΈ Rich content β images, video, embeds (click-to-load), lists, blockquotes, code, and tables render inline. Text is read aloud; visuals are shown silently.
- π Full Markdown (CommonMark + GFM via marked) with
inline formatting (bold, italic, links, inline codeβ¦) preserved and real
heading levels (
h1βh6). All HTML is sanitized against an allowlist. - π Dark / sepia / high-contrast themes, focus dimming, text-size, line-spacing, and speed controls.
- π Feed it Markdown, plain text, or a structured
{title, blocks}object. No fetching, no parsing assumptions β you bring the content however you like, and the component makes zero network calls. - βΏ ARIA roles,
aria-currenton the active sentence, full keyboard support, andprefers-reduced-motionhandling. - π¦ Zero build step required β works straight from a CDN with an import map.
npm install readalong-reader
# optional, only for the natural neural voice:
npm install kokoro-jsOr use it with no build step via a CDN + import map (see the demo below).
<read-along-reader id="reader" engine="system" theme="warm" focus></read-along-reader>
<script type="module">
import 'readalong-reader'; // registers <read-along-reader>
const reader = document.getElementById('reader');
reader.loadMarkdown('# Title\n\nParagraph one. It will be read aloud, word by word.');
// also: reader.loadDoc({ title, blocks }) β or reader.loadText('plain text')
</script><script type="importmap">
{ "imports": { "kokoro-js": "https://cdn.jsdelivr.net/npm/kokoro-js/+esm" } }
</script>
<read-along-reader engine="kokoro"></read-along-reader>
<script type="module">
import 'https://cdn.jsdelivr.net/npm/readalong-reader/src/index.js';
document.querySelector('read-along-reader')
.loadMarkdown('# Hello\n\nThis reads aloud, fully on your device.');
</script>Don't want the natural voice? Drop the import map and use engine="system" β
kokoro-js is never fetched.
import 'readalong-reader'; // registers the element
import { parseMarkdown } from 'readalong-reader/parse';
const el = document.querySelector('read-along-reader');
el.loadDoc(parseMarkdown('# Title\n\nBody text.'));
el.engine = 'kokoro';
el.play();| Attribute | Values | Description |
|---|---|---|
engine |
system | kokoro |
Active voice engine. |
theme |
warm | dark | sepia | contrast |
Color theme. |
focus |
boolean (present/absent) | Focus dimming on/off. |
rate |
number (0.6β1.6) | Speech speed. |
markdown |
string | Initial Markdown to read. |
text |
string | Initial plain text to read. |
kokoro-module |
URL or specifier | Where to import kokoro-js from (e.g. a CDN URL). |
loadMarkdown(md), loadDoc(doc), loadText(text), load(input) (auto-detects
doc / Markdown / text), play(), pause(), toggle(), stop(), restart(),
startFrom(index). There's also a markdown property (get/set).
All bubble and cross the shadow boundary: rar:load, rar:play, rar:pause,
rar:stop, rar:end, rar:sentence ({index, text}), rar:enginechange,
rar:status.
Override any CSS custom property on the host:
read-along-reader { --accent:#7cc6ff; --word:#7cc6ff; --maxw:680px; --fontsize:22px; }The reader never fetches anything β you decide where text comes from:
import { parseMarkdown } from 'readalong-reader/parse';
// 1) From your own Markdown
reader.loadMarkdown(myMarkdownString);
// 2) From a CMS / structured data β skip parsing entirely
reader.loadDoc({ title: 'My Post', blocks: [{ h2: 'Intro' }, { p: 'Hello.' }] });
// 3) Fetch + clean an article yourself, on your terms (e.g. a readability proxy)
const md = await (await fetch('https://r.jina.ai/' + articleUrl)).text();
reader.loadDoc(parseMarkdown(md));The structured doc is { eyebrow?, title?, blocks: [...] }. Each block is one of
{p}, {heading:{level,text|html}} (or legacy {h2}), {quote}, {code,lang?},
{hr}, {rawHtml}, {list:{ordered?,items}}, {img:{src,alt?,caption?}},
{video:{src,poster?,caption?}}, {embed:{url,provider?,title?,caption?}}, or
{table:{rows}}. Text values (p/heading/quote/list items) may be a plain
string or { html } with inline markup. Text blocks are read aloud; images,
video, embeds, code, tables, and raw HTML are rendered but not narrated. All HTML
is sanitized by the component.
Implement the small TTSEngine interface (see src/engines/base.js) and pass an
instance in. An engine drives word highlighting via onWord(index) and signals
completion via onEnd(); the reader owns sentence sequencing. This is how the
estimated-timing Kokoro engine and the exact-timing system engine share one code
path.
- First run downloads the model once (~300 MB at
fp32, then cached and offline). - On WebGPU the engine uses
fp32deliberately: theq8quantized model sounds garbled ("Simlish") on that backend. WASM uses the smallerq8. - Kokoro returns finished audio per sentence with no word-boundary events, so
word highlighting is estimated from the audio clock (sentence highlighting
is always exact). For exact word timing, use
engine="system".
The reader makes no network calls. Speech is generated on your device, and content is whatever you pass in. (The optional Kokoro engine downloads its model once from a CDN / Hugging Face on first use, then runs offline.) If you choose to fetch articles, that happens in your code β see "Bring your own content".
systemengine: any browser with the Web Speech API (incl. Safari).kokoroengine: a WebGPU browser (Chrome/Edge, recent) for best speed; falls back to WASM, and to the system engine if the model can't load.
These are planned next steps β contributions welcome:
- Exact word timing for Kokoro via the timestamped model variant.
- More voices/languages, and bookmark/resume of reading position.
- Optional spoken alt-text/captions for visuals (currently rendered silently).
Issues and PRs welcome β accessibility feedback especially. Please keep the
TTS/render core dependency-free (the only runtime dependency is marked, used
solely by the Markdown parser) and the component usable without a build step.
GPL-3.0-or-later Β© Bruno Silva.
Built with care for the low-vision community. If it helps you, that's the point.