English · Türkçe
a11y-magnifier
A zero-dependency, cursor-following screen magnifier widget for the web. Built for low-vision accessibility on news sites and other text-heavy pages — drop in a script tag, no build step, no framework required.
Live Storybook demo — try every option
Why this exists
Low-vision readers are a huge, under-served slice of the web's audience, and most sites have no magnification option beyond the browser's own zoom — which breaks layouts and loses the point you were reading. The alternative is usually an expensive SaaS overlay widget. This library is the free, open, embeddable middle ground: a lens that follows the cursor, keeps text sharp, and can be added to any page — including ones you don't control the build pipeline for — in one line.
It doesn't replace a full accessibility audit, but a pointer-driven magnifier is concretely useful for low-vision users today, and every site should be able to add one without a procurement process.
Regulatory context (EU & Turkey)
Accessibility is moving from "nice to have" to "legally required" for a growing share of the web, on both fronts this library targets:
- EU — public sector: the Web Accessibility Directive (EU) 2016/2102 has required WCAG 2.1 AA conformance for public sector websites and apps since 2019–2021 (rollout dates varied by site age).
- EU — private sector: the European Accessibility Act (EU) 2019/882 has applied since 28 June 2025, extending WCAG 2.1 AA-equivalent requirements (via EN 301 549) to e-commerce, banking, telecom, and other consumer-facing digital services sold in the EU — with fines for non-compliance.
- Turkey: Law No. 5378 on Persons with Disabilities and the Erişilebilirlik İzleme ve Denetleme Yönetmeliği (Official Gazette 20.07.2013 No. 28713, amended 21.09.2016 No. 29834) already require public institutions' digital services to be monitored for accessibility.
None of these name "a screen magnifier" as a specific requirement, and scope details matter — read the source text before treating any of this as compliance advice. But the direction is consistent: WCAG 2.1 AA is becoming the baseline across an increasing share of public and private sites, and pointer-driven magnification directly supports WCAG success criteria like 1.4.4 Resize Text and 1.4.10 Reflow. Adding a tool like this ahead of an audit or a legal deadline is cheap insurance, not overengineering.
Features
- Zero runtime dependencies, written in TypeScript, ~11 KB minified
- Follows the mouse cursor; optionally hides the native cursor while active
- Circle, square, or rectangle lens shapes
- Canvas-based rendering by default (SVG
foreignObjectsnapshot →<canvas>) — doesn't touch the live DOM, GPU-accelerateddrawImagezoom - Every option is live-tunable at runtime via
setOptions() - Configurable keyboard shortcut (default
Alt+M) - Toggle from a button, or auto-enable with
autoStart: true -
excludeSelectorshides the lens over ad slots, video players, or any other region you specify - Full type definitions generated straight from source (
dist/magnifier.d.ts) — no@typespackage needed - Full interactive Storybook covering every constructor option
Install
npm install a11y-magnifier
// CommonJS
const Magnifier = require('a11y-magnifier');
// ESM / bundlers (webpack, Vite, Rollup, Next.js, ...)
import Magnifier from 'a11y-magnifier';
Or skip the build step entirely and load it straight from a CDN — this bundle has no module format requirements at all, it just sets window.Magnifier:
<script src="https://cdn.jsdelivr.net/npm/a11y-magnifier/dist/magnifier.global.min.js"></script>
<script>
const mag = new Magnifier({ shape: 'circle', zoom: 2 });
mag.enable();
</script>
Quick start
// Button-controlled
const mag = new Magnifier({ shape: 'circle', size: 220, zoom: 2 });
document.getElementById('magBtn').addEventListener('click', () => mag.toggle());
// Active as soon as the page loads
new Magnifier({ autoStart: true, shape: 'circle', zoom: 2.5 });
API
new Magnifier(options)
| Option | Type | Default | Description |
|---|---|---|---|
shape |
'circle' | 'square' | 'rectangle' |
'circle' |
Lens shape |
size |
number |
220 |
Diameter/side for circle and square (px) |
width |
number |
360 |
Width for rectangle |
height |
number |
220 |
Height for rectangle |
zoom |
number |
2 |
Magnification factor |
borderWidth |
number |
3 |
Border thickness |
borderColor |
string |
rgba(255,255,255,.95) |
Border color |
borderStyle |
string |
'solid' |
CSS border-style |
borderRadius |
number |
8 |
Corner radius for square/rectangle |
shadow |
string |
'0 10px 40px ...' |
CSS box-shadow |
background |
string |
'#ffffff' |
Lens background |
crosshair |
boolean |
false |
Show a center-point indicator |
crosshairColor |
string |
rgba(255,0,0,.6) |
Crosshair color |
autoStart |
boolean |
false |
Enable automatically on page load |
hideCursor |
boolean |
true |
Hide the native page cursor while active |
smooth |
boolean |
true |
Animate lens movement |
smoothDuration |
number |
50 |
Transition duration (ms) |
offsetX / offsetY |
number |
0 |
Lens offset relative to the cursor |
zIndex |
number |
2147483646 |
Lens z-index |
renderMode |
'canvas' | 'clone' | 'auto' |
'canvas' |
Rendering strategy (see below) |
excludeSelectors |
string[] |
[] |
Selectors under which the lens hides (e.g. ['.ad-slot', '.video-ad']) |
refreshIntervalMs |
number |
500 |
Snapshot/clone refresh interval (ms) |
keyboardShortcut |
string | false |
'm' |
Key that toggles the magnifier; false disables it |
shortcutWithCtrl |
boolean |
false |
Require Ctrl/Cmd |
shortcutWithAlt |
boolean |
true |
Require Alt |
shortcutWithShift |
boolean |
false |
Require Shift |
onEnable |
function |
null |
Called when enabled |
onDisable |
function |
null |
Called when disabled |
onMove |
function |
null |
Called with (x, y) cursor coordinates |
renderMode
| Value | Description |
|---|---|
'canvas' |
The page is snapshotted into a <canvas> via an SVG foreignObject; no second DOM tree is created inside the lens, and magnification uses GPU-accelerated drawImage. Recommended. |
'clone' |
document.body is cloned into the lens and scaled with a CSS transform: scale(). More reliable on pages with cross-origin images. |
'auto' |
Uses 'canvas' when supported, otherwise falls back to 'clone'. |
Methods
| Method | Description |
|---|---|
.enable() |
Turn the magnifier on |
.disable() |
Turn the magnifier off |
.toggle() |
Toggle on/off |
.isEnabled() |
Whether it's currently on |
.setOptions(opts) |
Update options at runtime |
.getOptions() |
Get the current options |
.destroy() |
Remove all listeners permanently |
Keyboard shortcuts
| Shortcut | Action |
|---|---|
Alt + M |
Toggle the magnifier (default) |
Esc |
(not built-in — wire it yourself, see below) |
The shortcut is ignored while focus is inside an input, textarea, or contenteditable element.
Real-world scenario: a news site
const mag = new Magnifier({
shape: 'circle',
size: 240,
zoom: 2.2,
renderMode: 'canvas',
autoStart: false,
excludeSelectors: ['.ad-slot', '.gpt-ad', '.video-player', '[data-ad]'],
keyboardShortcut: 'm',
shortcutWithAlt: true,
});
// Wire it to an accessibility menu toggle
document.querySelector('#accessibility-magnifier-toggle')
.addEventListener('click', () => mag.toggle());
// Optional: let Esc close it too
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && mag.isEnabled()) mag.disable();
});
Notes & limitations
- Canvas mode (default): page content is written into a canvas via an SVG
foreignObject; no second DOM tree is created inside the lens.drawImagemagnification is GPU-accelerated. Cross-origin<img>or<iframe>elements cannot be captured in the snapshot (a browser security restriction) — this rarely matters for a news site's own article content. - Clone mode: applies a CSS scale to a DOM clone of the page.
<canvas>elements and cross-origin<iframe>content may not appear magnified in this mode either. Prefer this mode if your page serves cross-origin images. - Setting
refreshIntervalMsvery low increases CPU usage; the default (500ms) is fine for most pages. - The keyboard shortcut never fires while typing in an input, textarea, or contenteditable element.
Development
The source is TypeScript (src/), built with tsup into four dist/ bundles: magnifier.js (CJS), magnifier.mjs (ESM), magnifier.global.js / magnifier.global.min.js (IIFE, sets window.Magnifier — used by the CDN snippet above), plus generated .d.ts types.
npm install
npm run storybook # interactive playground — every option, live
npm run typecheck # tsc --noEmit
npm run build # produces the dist/ bundles described above
npm run build-storybook # static Storybook build (used by CI/Pages deploy)
Contributions, issues, and ideas are welcome — open a PR or an issue on GitHub.
License
MIT Umut Yaldız
Author
Umut Yaldız — github.com/umutyaldiz