One profile, accessible
on every page.
Accessibility on the web is fragmented — every site asks you to reconfigure font size, contrast, and reading aids from scratch. Lens fixes that: one profile follows you everywhere. I designed and built the extension, marketing site, design system, and Chrome Web Store submission package solo.
Open the live site
The marketing site with a live before/after demo. Install the extension to carry your profile across every page you visit.
Design system
Forest-green parchment tokens, Atkinson Hyperlegible type, accessible radii, the aperture mark — the full, code-truthful spec.
Under the hood
Manifest V3 · Cloudflare Workers · Anthropic API · Shadow DOM. A stateless backend, encrypted sync, and a full MV3 extension — zero auth friction.
Accessibility is fragmented. Every page starts from zero.
Roughly one in five people uses the web with some kind of accessibility need — larger text, higher contrast, a color-blindness filter, reduced motion, a simplified layout. The tools exist. The problem is that every tool is per-site, per-session, per-device. There's no memory, no continuity, no profile that follows you. Reader-mode helps on articles; it breaks on dashboards. System-level settings help with contrast; they can't reflow a dense form. Every accommodation is a fresh negotiation with the page.
One profile, five layers of adaptation.
I mapped the adaptation pipeline on paper first — from onboarding to a page that reshapes itself around you — before writing a single line of content script.
60 seconds,
no account
E2E-encrypted
sync optional
script
reading view
injected
Worker streams
Anthropic SSE
alt-text, forms —
all applied
The decisions that shaped everything else.
"One profile, everywhere" is easy to say. Making it trustworthy, instant, and invisible required three early bets — on where data lives, how fast the first page adapts, and what the extension must never do.
Profile lives on the device, not a server.
Your font size, contrast level, color-blindness filter, and reading preferences are stored in chrome.storage.local — no account, no server, nothing to leak. The optional cross-device sync uses AES-GCM encryption with a key that never leaves your devices. The adaptation API is stateless: it receives the page content and profile settings per call, stores nothing.
Adaptation streams — the page moves as it renders.
The Cloudflare Worker streams Anthropic's SSE response directly to the content script. The reading view starts populating within ~200 ms of clicking "Adapt this page" — paragraphs appear one by one as the model produces them. There's no blank white screen followed by a page swap. The incremental feel makes it obvious something real is happening, and makes a 2–3 s total latency feel fast.
The extension never reads sensitive pages.
A hardcoded domain allowlist (banking, medical, government, legal) silently skips adaptation without prompting. The sensitive-domain logic lives in the profile layer, not the UI, so it can't be accidentally overridden. No content from those domains is ever sent to the backend — the popup simply shows "Lens is paused here for your safety" and offers a manual override for power users who knowingly want it.
Small calls that change how the whole thing feels.
Accessibility tooling has a trust problem — most of it feels clinical, brittle, or broken. The micro-decisions below are about earning trust one interaction at a time.
Onboarding asks, never assumes.
Seven conversational questions in ~60 seconds: reading level, contrast preference, color-blindness type, preferred font size, whether you use a screen reader, whether you want form simplification, and a free-text "anything else?" The profile is populated from answers, not inferred from browsing. Users know exactly what Lens knows about them — because they typed it.
"Show original" is always one click away.
The floating Lens badge stays pinned in the corner after adaptation. One click toggles back to the original page. There's no cognitive cost to trying an adaptation — you can always undo it instantly. This made users far more willing to try the "full reflow" mode in testing, because the worst case is one click to reverse.
Color-blindness filters use actual CSS SVG matrices.
Eight filters — Protanopia, Deuteranopia, Tritanopia, Protanomaly, Deuteranomaly, Tritanomaly, Achromatopsia, and Achromatomaly — are each a precise SVG feColorMatrix applied as a root-level CSS filter. They're exact perceptual simulations, not approximations. Selecting one is instantaneous, non-destructive, and reversible.
Form simplification is opt-in per form, not global.
The form-simplification module presents one question at a time with a large text field. Activating it on a specific form doesn't affect anything else on the page. Users who want the simplified view can trigger it on a dense checkout form; users who need to see all fields at once aren't penalised. Accessibility shouldn't override agency.
AI alt-text labels the gap, not the image.
When the AI alt-text module encounters an unlabeled image, it generates a description and injects it as the actual alt attribute — not a tooltip, not a caption. The injected text is prefixed with "(AI-generated)" so screen reader users know the provenance. Honest about its limits; useful where it counts.
The screen-reader companion mode is cooperative, not competitive.
Lens's companion mode works alongside NVDA, JAWS, and VoiceOver — it doesn't try to replace them. It patches missing ARIA roles, injects landmark headings where the page has none, and normalises reading order. The extension announces itself to the active screen reader via a live region and defers to the reader's navigation model. An accessibility extension that breaks screen readers was the one thing I refused to ship.
One legible system, used everywhere.
Lens runs on a "forest canvas" system — warm parchment background, near-black ink, deep-forest-green primary, and sage as the secondary. Atkinson Hyperlegible is the type family throughout: it was designed specifically for low-vision readers, which makes it not just on-brand but the right technical choice. The full spec lives on the Design page.
Green that communicates safety. Parchment that rests the eye.
The palette is built around low-contrast fatigue — warm parchment backgrounds (#faf9f5) instead of cold white, near-black ink (#141413) instead of pure black. The deep forest green carries trust and calm without the clinical coldness of a standard blue primary. Sage is used only for secondary actions and highlights.
Atkinson Hyperlegible — the accessible choice is the right choice.
Designed by the Braille Institute specifically for readers with low vision. Exaggerated letterform distinctions — I vs l vs 1, O vs 0 vs D — reduce misreads without looking clinical. It's the only typeface that earns a spot in an accessibility product on merit, not aesthetics.
One family at regular weight for body text. The same unambiguous letterforms carry through to every piece of instructional copy in the extension.
Mono only where a label needs to feel technical — section eyebrows, API version tags, and the design-spec voice you're reading now.
Soft but not ambiguous.
Corners are rounded to reduce visual sharpness on high-contrast displays, but not so round that buttons and cards lose their shape distinction. Four steps, from the pill badge down to the smallest chip.
A lens. A reading eye. The same shape.
The mark is a forest-green circle with two concentric rings — part optical lens, part reading eye, part aperture. It works at 16 × 16 px in the browser toolbar and at marketing-page scale. The forest-green radial gradient is the single loudest brand element; every other decision defers to legibility. Icons throughout the extension use a consistent 1.5 px stroke with rounded caps, matching the body weight of Atkinson Hyperlegible.
Motion that explains, not entertains.
The cuts I'd defend.
An accessibility product needs to earn trust before it earns features. Every item below was a real temptation — and a real cut made on purpose.
Your profile is on your device, not our server. A cloud profile would enable seamless cross-device sync without any friction — but it would also mean a database of every user's disability profile, a breach target, and a GDPR Art. 9 liability. The trade-off is explicit: local-first by default, opt-in encrypted sync for power users. No server, no leak.
Lens never silently observes your reading behaviour. The per-site fine-tuning module (site-tuning.js) tracks only explicit manual adjustments the user makes — not reading time, scroll depth, or whether they toggled back to original. On-device only. Nothing inferred without action.
Adaptation only runs when you click "Adapt this page." An always-on mode that adapts every page on load would be faster, but would also consume API credits, introduce latency on pages you don't need adapted, and trigger on pages where adaptation is harmful. Explicit beats automatic for an accessibility tool — you should always know when Lens is active.
No shared profiles, no community presets, no leaderboard of "most accessible sites." These features would add engagement but would require accounts, moderation, and infrastructure. The first goal is for one person to have a better reading experience on one page. Everything else comes after that works reliably.
Built for cross-browser, built for privacy.
One MV3 extension ships to Chrome and Edge on day one, with Firefox and Safari as first-tier targets. The backend is a stateless Cloudflare Worker that never logs content — streaming Anthropic's API without storing a word of what it processes.