Chrome Extension (Manifest V3) that converts web articles into a distraction-free reader view with adjustable typography, guided reading, text-to-speech, and optional LLM-powered simplification.
People with dyslexia struggle with default web typography — long lines, tight spacing, cluttered layouts. This extension extracts article content from any page and re-renders it with configurable dyslexia-friendly settings. It also breaks articles into manageable chunks, offers word explanations, and reads content aloud.
- Reader View — strips page clutter, renders article in a clean layout
- Typography controls — font size, line spacing, letter spacing, word spacing, line width
- Theme switching — cream, gray, dark backgrounds
- OpenDyslexic font — toggle on/off
- Guided reading — splits articles into chunks (80–180 words each), navigate with Prev/Next
- Chunk simplification — rewrites a chunk into shorter sentences, bullet points, and a glossary (requires OpenRouter API key; local fallback available)
- Explain selection — select any word or phrase, get a plain-language explanation in a tooltip
- Glossary — per-article word list, built automatically from explanations
- Reading ruler — focus overlay in line or paragraph mode, navigate with J/K, dismiss with Esc
- Text-to-speech — Web Speech API (built-in, free) with optional ElevenLabs support
- Local fallbacks — all features work without an API key using local paragraph/sentence splitting and basic text processing
Requirements: Node.js >= 18, Chrome or Chromium-based browser.
npm install
npm run buildThis creates a dist/ folder.
- Go to
chrome://extensions - Enable Developer mode (top-right)
- Click Load unpacked → select the
dist/folder
npm run dev # watch mode, rebuilds on save
npx tsc --noEmit # typecheck without emitting- Go to any article page
- Click the extension icon → Enable Reader Mode
- Adjust typography with the toolbar sliders and buttons
- Click the guided reading button to split the article into chunks
- Select text → click Explain pill for a tooltip definition
- Toggle the reading ruler for line/paragraph focus
- Click play to hear the current chunk read aloud
Go to extension Settings → enter an OpenRouter API key. This enables smarter chunking, richer simplification, and detailed explanations. Without it, local fallbacks handle everything.
The extension has 5 entry points and 11 feature modules. Communication flows through chrome.runtime messaging, with the background service worker as the central orchestrator.
Popup ──▶ Background Service Worker ──▶ Content Script (injected on-demand)
│
┌───────────┼───────────────┐
▼ ▼ ▼
Reader Page Options Page OpenRouter API
| Entry | Path | Role |
|---|---|---|
| Background | src/background/index.ts |
Service worker, handles all message routing, makes LLM calls, manages cache |
| Content script | src/content/index.ts |
Injected into active tab on-demand, extracts article HTML/text |
| Popup | src/popup/ |
Toolbar popup with "Enable Reader Mode" button and quick toggles |
| Options | src/options/ |
API key input, model selection, default style prefs, cache controls |
| Reader | src/reader/ |
Main reading UI — article display, toolbar, guided panel, glossary |
Each module lives in src/modules/<name>/index.ts with a single-file interface. Modules are isolated — you can modify one without breaking others.
src/modules/
├── extractor/ Extracts article body using vendored Readability + HTML sanitizer
├── readerStyle/ Applies CSS custom properties for typography/theme
├── llmClient/ Thin fetch wrapper for OpenRouter chat completions API
├── chunker/ Splits articles into guided reading chunks (LLM or local)
├── simplifier/ Rewrites chunk text into simpler language (LLM or local)
├── explainer/ Generates word/phrase explanations (LLM or local)
├── glossary/ Per-article glossary CRUD via chrome.storage
├── readingRuler/ DOM overlay for line/paragraph focus
├── tts/ Text-to-speech via Web Speech API + optional ElevenLabs
├── storage/ Wrapper for chrome.storage.sync (settings) and .local (cache)
└── messaging/ Typed wrappers for chrome.runtime.sendMessage/onMessage
Module dependency graph:
extractor → lib/readability, lib/sanitize
readerStyle → (standalone)
llmClient → (standalone, fetch only)
chunker → llmClient
simplifier → llmClient
explainer → llmClient
glossary → storage
readingRuler → (standalone, DOM only)
tts → (standalone, Web APIs)
storage → (standalone, chrome.storage)
messaging → (standalone, chrome.runtime)
All data contracts and message types are in src/shared/types.ts. If you add a new message type:
- Add the message interface to
types.ts - Add it to the
ExtMessageunion - Handle it in
src/background/index.ts
- Create
src/modules/<name>/index.ts - Export functions with clear signatures
- Import shared types from
@shared/types - Wire it into the background handler if it needs message-based communication
- Modules should not import from other modules except
llmClient,storage, ormessaging
Dyslexia-Web-Reader/
├── public/
│ ├── manifest.json Manifest V3 configuration
│ ├── icons/ Extension icons (16/48/128px)
│ └── assets/fonts/ OpenDyslexic-Regular.woff2
├── src/
│ ├── shared/types.ts All interfaces, message types, defaults
│ ├── lib/
│ │ ├── readability.ts Vendored article extractor
│ │ └── sanitize.ts HTML allowlist sanitizer
│ ├── modules/ 11 feature modules (see above)
│ ├── background/index.ts Service worker
│ ├── content/index.ts Content script
│ ├── popup/ popup.html, popup.ts, popup.css
│ ├── options/ options.html, options.ts, options.css
│ └── reader/ reader.html, reader.ts, reader.css
├── vite.config.ts Multi-entry build config + HTML move plugin
├── tsconfig.json
└── package.json
| Permission | Why |
|---|---|
activeTab |
Access the current tab's page content when the user clicks the extension |
storage |
Persist settings, cached articles, reading plans, glossary |
scripting |
Inject the content script on-demand (not persistent) |
host: openrouter.ai |
LLM API calls |
host: elevenlabs.io |
Optional TTS API |
No browsing data is collected. Articles are processed only on explicit user action. API keys are stored locally in chrome.storage.sync, never transmitted anywhere except to the configured API endpoint.
- TypeScript (strict mode)
- Vite (multi-entry bundler)
- Chrome Extension Manifest V3
- Zero runtime npm dependencies (Readability and sanitizer are vendored)
MIT