@preepo/allykit
v1.3.0
Published
AllyKit — a lightweight, zero-dependency accessibility toolkit for any webpage. Text size, contrast modes, dyslexia font, screen reader, ADHD focus mode, and more.
Maintainers
Readme
AllyKit
A lightweight, zero-dependency accessibility toolkit you drop into any webpage with a single <script> tag. It gives users a floating button that opens a panel of accessibility tools — text size, contrast modes, dyslexia font, screen reader, ADHD focus mode, and more — all persisted to localStorage so preferences carry across pages.
Version 1.3.0
Why AllyKit?
- 🚀 Zero dependencies — Pure vanilla JavaScript, no framework required
- 🎨 Fully customizable — Brand colors, position, and feature toggles
- 💾 Persistent preferences — Settings stored in localStorage across sessions
- ♿ WCAG 2.2 compliant — Auto-fixes common accessibility issues
- 🌐 Framework agnostic — Works with React, Vue, Angular, or plain HTML
- 📦 Tiny footprint — ~20KB minified and gzipped
- 🔧 No build required — Works with a single
<script>tag
Table of Contents
- Why AllyKit?
- Features
- Demo
- Installation
- Usage
- Configuration
- Feature reference
- JavaScript API
- Keyboard shortcuts
- How settings are stored
- Browser support
- Troubleshooting
- Contributing
- License
Features
| Feature | What it does | WCAG Criteria |
|---|---|---|
| Text size | Scales page text to 110%, 120%, 130%, or 140% | 1.4.4 (AA) |
| High contrast | Four modes: Enhanced · Black/White · Black/Yellow · Dark Mode | 1.4.3, 1.4.6 (AA/AAA) |
| Text spacing | Three levels of letter + word spacing (Loose → Widest) | 1.4.12 (AA) |
| Line height | Three levels: 1.5× · 1.8× · 2× | 1.4.12 (AA) |
| Dyslexia friendly | Switches body text to OpenDyslexic font | 1.4.8 (AAA) |
| ADHD mode | Adds a focus strip that follows the cursor to reduce distraction | 2.4.11 (AA) |
| Saturation | Low · High · Grayscale | 1.4.1 (A) |
| Invert colors | CSS invert + hue-rotate over the whole page | 1.4.1 (A) |
| Big cursor | Replaces the system cursor with an enlarged one | 2.5.5 (AAA) |
| Hide images | Hides <img>, <picture>, and CSS background images | User preference |
| Pause animations | Collapses all CSS animations and transitions to 0ms | 2.3.3 (AAA) |
| Highlight links | Wraps every <a> in a bright yellow outline | 2.4.7 (AA) |
| Screen reader | Uses Web Speech API to announce hovered, focused, and clicked elements | 1.3.1, 4.1.2 (A) |
| WCAG Auto-fixer | Automatically fixes common accessibility issues (missing alt text, labels, ARIA attributes) | Multiple |
All 13 features are enabled by default. Any subset can be disabled via config.
Demo
🎯 Live Demo — Try all features in action
Or run locally:
# Clone the repo
git clone https://github.com/pritush/ally-kit.git
cd ally-kit
# Open in browser
open index.html # macOS
start index.html # Windows
xdg-open index.html # LinuxInstallation
Plain HTML (no build step)
Use ally-kit.js for development (readable, with comments) or ally-kit-min.js for production (minified, smaller payload):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Accessible Website</title>
</head>
<body>
<h1>Welcome</h1>
<p>Your content here...</p>
<!-- Add AllyKit at the end of body -->
<script src="ally-kit.js"></script>
</body>
</html>For production (minified, ~20% smaller):
<script src="ally-kit-min.js"></script>
</body>AllyKit self-initializes on DOMContentLoaded and attaches to window.AllyKit.
npm / yarn / pnpm
# npm
npm install @preepo/allykit
# yarn
yarn add @preepo/allykit
# pnpm
pnpm add @preepo/allykitCDN
Via GitHub (works as soon as the repo is public — no publish needed):
<!-- Unminified -->
<script src="https://cdn.jsdelivr.net/gh/pritush/ally-kit@main/ally-kit.js"></script>
<!-- Minified -->
<script src="https://cdn.jsdelivr.net/gh/pritush/ally-kit@main/ally-kit-min.js"></script>Via npm (works after npm publish):
<!-- Unminified -->
<script src="https://cdn.jsdelivr.net/npm/@preepo/allykit/ally-kit.js"></script>
<!-- Minified -->
<script src="https://cdn.jsdelivr.net/npm/@preepo/allykit/ally-kit-min.js"></script>Usage
Plain <script> tag
Configure with window.AllyKitConfig before the script tag:
<script>
window.AllyKitConfig = {
accentColor: "#16a34a",
features: { adhd: false }
};
</script>
<script src="ally-kit.js"></script>
<!-- window.AllyKit is now available -->ES module (Vite, webpack, Rollup, etc.)
import AllyKit from '@preepo/allykit';
// init() must be called manually in module context.
// Pass options directly — window.AllyKitConfig is not read.
const kit = AllyKit.init({
accentColor: '#7c3aed',
position: 'left',
features: {
adhd: false,
hideImages: false
}
});
// kit is the full API object
kit.open();CommonJS (Node-compatible bundlers)
const AllyKit = require('@preepo/allykit');
const kit = AllyKit.init({
accentColor: '#ea580c'
});React example
import { useEffect, useRef } from 'react';
import AllyKit from '@preepo/allykit';
export function AccessibilityToolkit() {
const kitRef = useRef(null);
useEffect(() => {
kitRef.current = AllyKit.init({
accentColor: '#4f46e5',
position: 'right',
features: { adhd: false }
});
return () => {
kitRef.current?.destroy();
};
}, []);
return null; // AllyKit renders its own DOM
}Vue example
import AllyKit from '@preepo/allykit';
import { onMounted, onUnmounted } from 'vue';
export function useAllyKit(options = {}) {
let kit = null;
onMounted(() => {
kit = AllyKit.init(options);
});
onUnmounted(() => {
kit?.destroy();
});
return { kit };
}SSR note — AllyKit renders real DOM into
document.bodyvia Shadow DOM. It requires a browser environment. With Next.js, Nuxt, or any SSR framework make sureinit()is only called client-side (insideuseEffect,onMounted, or a"use client"boundary).
Configuration
All options at a glance
<script>
window.AllyKitConfig = {
// Position and layout
position: "right", // "left" | "right"
buttonOffset: "24px", // Distance from the screen edge
panelWidth: "560px", // Width of the settings panel
accentColor: "#4f46e5", // Any 3- or 6-digit CSS hex colour
// WCAG auto-fixer
autoFix: true, // Enable automatic accessibility fixes
autoFixLang: "en", // Default lang attribute if missing
// Feature toggles (all enabled by default)
features: {
textSize: true,
highContrast: true,
textSpacing: true,
lineHeight: true,
dyslexia: true,
adhd: true,
saturation: true,
invert: true,
bigCursor: true,
hideImages: true,
pauseAnimation: true,
highlightLinks: true,
screenReader: true
}
};
</script>
<script src="ally-kit.js"></script>You only need to list the values you want to change — everything omitted falls back to its default.
Layout options
| Option | Type | Default | Description |
|---|---|---|---|
| position | "left" | "right" | "right" | Which side of the screen the button and panel appear on |
| buttonOffset | CSS string | "24px" | Distance of the button from the screen edge |
| panelWidth | CSS string | "560px" | Width of the settings panel |
Accent colour
| Option | Type | Default | Description |
|---|---|---|---|
| accentColor | 3- or 6-digit hex string | "#4f46e5" | Primary brand colour for the toolkit |
AllyKit derives a complete four-token palette from this single value at runtime:
| CSS custom property | Role | Derived from |
|---|---|---|
| --ak-primary | Button background, active borders, step indicators, header icon | accentColor directly |
| --ak-on-primary | Text/icons on primary surfaces | White or black — whichever achieves the higher WCAG contrast ratio against accentColor |
| --ak-primary-container | Active feature card background, Reset button chip | 82 % tint toward white |
| --ak-on-primary-container | Text inside primary-container surfaces | 70 % shade toward black |
The focus ring (--ak-focus) also tracks --ak-primary, so keyboard navigation stays on-brand.
All neutral surfaces (--ak-surface, --ak-surface-container, --ak-on-surface, etc.) are fixed greys and are not affected by accentColor.
Quick examples:
accentColor: "#4f46e5" // Indigo (default)
accentColor: "#16a34a" // Brand green
accentColor: "#ea580c" // Warm orange
accentColor: "#7c3aed" // Deep violet
accentColor: "#3b82f6" // Slate blue
accentColor: "#0891b2" // CyanIf
accentColoris missing, invalid, or not a hex value, AllyKit silently falls back to"#4f46e5".
Feature toggles
Set any feature key to false to hide it from the panel entirely. The feature's body class and CSS filter are never applied when disabled.
<script>
window.AllyKitConfig = {
features: {
invert: false,
saturation: false,
hideImages: false
}
};
</script>
<script src="ally-kit.js"></script>Common recipes
Minimal set — just the essentials:
window.AllyKitConfig = {
features: {
textSize: true,
highContrast: true,
dyslexia: true,
pauseAnimation: true,
screenReader: true,
textSpacing: false,
lineHeight: false,
adhd: false,
saturation: false,
invert: false,
bigCursor: false,
hideImages: false,
highlightLinks: false
}
};E-commerce — preserve product imagery and brand palette:
window.AllyKitConfig = {
features: {
invert: false,
saturation: false,
hideImages: false
}
};Blog / docs — reading-focused:
window.AllyKitConfig = {
features: {
adhd: false,
bigCursor: false,
invert: false,
saturation: false,
hideImages: false
}
};Web app — avoid obscuring UI chrome:
window.AllyKitConfig = {
features: {
adhd: false, // reading mask covers interactive elements
hideImages: false // icons are functional, not decorative
}
};WCAG Auto-fixer
AllyKit includes a built-in WCAG 2.2 auto-fixer that scans your page and automatically fixes common accessibility issues:
| Issue | Auto-fix |
|---|---|
| Missing alt attributes on images | Adds alt="" for decorative images or descriptive text from context |
| Form inputs without labels | Generates labels from nearby text or adds aria-label |
| Missing lang attribute on <html> | Adds default language (configurable via autoFixLang) |
| Links/buttons without accessible names | Adds aria-label from visible text or context |
| Duplicate IDs | Renames duplicates to ensure uniqueness |
| Missing ARIA roles | Adds semantic roles where appropriate |
| Redundant role attributes | Removes roles that match native semantics |
| Skip-to-content link | Adds a keyboard-accessible skip link if missing |
Configuration:
window.AllyKitConfig = {
autoFix: true, // Enable/disable auto-fixer (default: true)
autoFixLang: "en" // Default lang attribute (default: "en")
};The auto-fixer runs on page load and monitors for dynamically added content. Fixed elements are marked with data-allykit-fixed attribute. A badge displays the number of issues fixed.
Note: Auto-fixes are client-side only and don't modify your source code. For permanent fixes, address issues at the source.
Feature reference
Text size
Applies a CSS zoom multiplier to every direct child of <body> except the AllyKit root itself.
| Level | Value | |---|---| | 0 (default) | 100 % | | 1 | 110 % | | 2 | 120 % | | 3 | 130 % | | 4 | 140 % |
High contrast
Adds a body class that overrides background, foreground, border, and link colours with !important rules. Level 4 (Dark Mode) is a WCAG AA–compliant dark theme.
| Level | Name | Background | Foreground |
|---|---|---|---|
| 1 | Enhanced | #000 | #fff |
| 2 | Black / White | #000 | #fff (thicker link underline) |
| 3 | Black / Yellow | #000 | #ffff68 |
| 4 | Dark Mode | #121212 | #e0e0e0 |
Text spacing
Adds letter-spacing and word-spacing to all text elements (p, h1–h6, li, a, button, td, etc.).
| Level | Letter spacing | Word spacing | |---|---|---| | 1 (Loose) | 0.08 em | 0.16 em | | 2 (Wider) | 0.12 em | 0.24 em | | 3 (Widest) | 0.16 em | 0.32 em |
Line height
| Level | Multiplier | |---|---| | 1 | 1.5 × | | 2 | 1.8 × | | 3 | 2 × |
Dyslexia friendly
Switches the font stack of all text elements to OpenDyslexic (loaded from a .woff file). Falls back to Arial / system-ui if the font fails to load.
ADHD mode
Injects a full-width focus strip element that follows the mouse pointer vertically, dimming everything above and below the current line of text. The strip is a separate DOM element (#allykit-adhd-mask) that sits outside the Shadow DOM so it can overlay the page.
Saturation
Applies a CSS filter to all top-level body children.
| Level | Filter |
|---|---|
| 1 (Low) | saturate(0.45) |
| 2 (High) | saturate(1.9) |
| 3 (Grayscale) | grayscale(1) saturate(0) |
Invert colors
Applies invert(1) hue-rotate(180deg) — the hue rotation cancels the colour shift while keeping the luminance inversion. Composes correctly when used together with Saturation.
Pause animations
Sets animation-duration: 0.001ms, animation-iteration-count: 1, transition-duration: 0s, and scroll-behavior: auto on every element including ::before / ::after pseudo-elements. Also pauses any <video> and <audio> elements that were playing, and resumes them when the feature is turned off.
Screen reader
Uses window.speechSynthesis (Web Speech API). When active:
- Hover — announces interactive elements (
a,button,input,select,textarea,[role=button],[role=link],[tabindex]) after a 150 ms debounce. - Focus (Tab key) — announces the newly focused element if it wasn't already announced by hover.
- Link clicks — for same-tab navigation, the click is intercepted, the link text is spoken, and navigation happens once speech ends (4 s safety timeout).
_blanklinks are announced but not intercepted.
Text extraction priority: aria-label → aria-labelledby → <label for> → alt / title → placeholder → value → innerText.
The screen reader is silently restored on page load if it was active in a previous session.
Browser note —
speechSynthesisis not available in all environments. AllyKit logs a warning and skips speech silently if unsupported; the toggle button is still shown.
JavaScript API
After initialisation, the API object is available as window.AllyKit (plain script) or as the return value of AllyKit.init() (module):
// Open the settings panel
window.AllyKit.open();
// Close the settings panel
window.AllyKit.close();
// Toggle the settings panel open / closed
window.AllyKit.toggle();
// Reset all settings to defaults and clear localStorage
window.AllyKit.reset();
// Read the current state (returns a plain object copy — safe to mutate)
const state = window.AllyKit.getState();
// {
// textSize: 0, highContrast: 0, textSpacing: 0, lineHeight: 0,
// dyslexia: false, adhd: false, saturation: 0, invert: false,
// bigCursor: false, hideImages: false, pauseAnimation: false,
// highlightLinks: false, screenReader: false
// }
// Current version string
console.log(window.AllyKit.version); // "1.2.0"
// Fully remove AllyKit from the page and clean up all side-effects
window.AllyKit.destroy();destroy() removes the AllyKit DOM root, disconnects the MutationObserver, removes all body classes and CSS filters, stops speech synthesis, and sets window.AllyKit to null. It does not clear localStorage — call reset() first if you want a clean slate.
Keyboard shortcuts
| Key | Action |
|---|---|
| Ctrl + F2 | Toggle the settings panel open / closed |
| Escape | Close the settings panel |
| Tab / Shift+Tab | Move focus through panel controls (focus is trapped inside the panel while open) |
| ↑ / ↓ | Move between feature buttons inside the panel |
How settings are stored
User preferences are saved to localStorage under the key allyKitSettings as a flat JSON object:
{
"textSize": 2,
"highContrast": 0,
"textSpacing": 1,
"lineHeight": 0,
"dyslexia": false,
"adhd": false,
"saturation": 0,
"invert": false,
"bigCursor": false,
"hideImages": false,
"pauseAnimation": false,
"highlightLinks": true,
"screenReader": false
}- Level features (
textSize,highContrast,textSpacing,lineHeight,saturation) store an integer0–Nwhere0is the default/off state. - Toggle features store a boolean.
- If
localStorageis unavailable (private browsing, storage quota exceeded, sandboxed iframe) AllyKit falls back gracefully — settings still work for the session, they just aren't persisted.
To clear saved settings programmatically:
window.AllyKit.reset();
// — or —
localStorage.removeItem("allyKitSettings");Browser support
| Browser | Minimum Version | Notes | |---|---|---| | Chrome / Edge | 88+ | Full support | | Firefox | 85+ | Full support | | Safari | 14+ | Full support | | Opera | 74+ | Full support | | Mobile Safari (iOS) | 14+ | Full support | | Chrome Android | 88+ | Full support |
Required browser features:
- Shadow DOM (for style encapsulation)
MutationObserver(for dynamic content tracking)localStorage(for preference persistence)SpeechSynthesisAPI (optional, for screen reader feature)
AllyKit gracefully degrades if optional features are unavailable. No polyfills required.
Troubleshooting
Widget doesn't appear
- Check console for errors — Open browser DevTools and look for JavaScript errors
- Verify script is loaded — Make sure the script path is correct and file is accessible
- Check for conflicting CSS — Ensure no other styles are hiding
#allykit-root - Test in a clean environment — Try on a minimal HTML page to rule out conflicts
Configuration not applied
- Define config before script —
window.AllyKitConfigmust be set before the<script>tag - Check config syntax — Ensure valid JavaScript object syntax (commas, quotes)
- Verify property names — Feature keys are case-sensitive
Screen reader not working
- Check browser compatibility — Some browsers don't support
SpeechSynthesis - Check permissions — Some browsers require user interaction first
- Test with simple content — Hover over a button or link to trigger speech
Styles bleeding in/out
AllyKit uses Shadow DOM to prevent style conflicts. If you see issues:
- Check for
!importantrules — These can pierce Shadow DOM boundaries - Verify widget isolation — The widget should be inside
#allykit-root
Performance issues
- Disable unused features — Set unused features to
falsein config - Limit auto-fixer scope — If you have many DOM nodes, auto-fixer might be slow
- Check for large pages — Text zoom and filters can be CPU-intensive on massive pages
Contributing
- Fork the repo and create a feature branch.
- Make your changes in
ally-kit.js— the entire toolkit lives in one self-contained UMD file. - Regenerate
ally-kit-min.jsfrom the updated source (e.g.npm run buildor your minifier of choice). - Test against
index.htmlwhich exercises every feature. - Open a pull request with a clear description of what changed and why.
Things to keep in mind
- AllyKit must remain a single, dependency-free file.
- All UI DOM lives inside a Shadow DOM root (
attachShadow({ mode: "open" })). Page styles must not bleed in. - Page-level effects (body classes, CSS filters, zoom) must be cleaned up by
destroy(). - New features should follow the existing
{ key, label, icon, type }descriptor pattern and have a matching entry indefaultStateandDEFAULT_CONFIG.features. - CSS custom properties inside the shadow root use the
--ak-prefix. - DOM IDs and body classes use the
allykit-prefix.
License
MIT © Pritush M.
