ally-widget
Ally Widget
A lightweight, framework-free accessibility widget for any website. Drop it in with a single <script> tag or import it as an npm package — it adds a floating panel of accessibility controls that users can customize per-session.
- No dependencies — vanilla JS, ~154 KB minified
- Three delivery formats — CDN (IIFE), ESM (bundlers), CJS (Node/SSR)
- Languages — English and Nepali built-in
- WCAG 2.x aligned — focus trapping, ARIA roles, keyboard navigation throughout
Installation
CDN / Direct script tag
<script src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"></script>Or download dist/ally-widget.min.js and host it yourself.
npm
npm install ally-widget// ESM bundler (Vite, webpack, Rollup…)
import AllyWidget from 'ally-widget';
// CommonJS (Node / SSR)
const AllyWidget = require('ally-widget');Quick Start
Script tag (zero config)
The widget auto-initialises on DOMContentLoaded. Just include the script:
<script src="dist/ally-widget.min.js"></script>A button appears at the bottom-right corner. Press Alt + A to toggle the panel.
Configure before loading
Set window.AllyWidgetOptions before the script tag:
<script>
window.AllyWidgetOptions = {
position: 'bottom-right',
lang: 'ne', // 'en' | 'ne'
primaryColor: '#e11d48',
poweredByText: 'My Company',
poweredByUrl: 'https://mycompany.com'
};
</script>
<script src="dist/ally-widget.min.js"></script>ESM / npm
import AllyWidget from 'ally-widget';
const widget = new AllyWidget({
position: 'bottom-left',
primaryColor: '#0ea5e9',
disableFeatures: ['hide-images', 'annotations'],
onFeatureToggle(key, enabled) {
analytics.track('a11y_toggle', { key, enabled });
}
});
widget.startAccessibleWebWidget();All Options
| Option | Type | Default | Description |
|---|---|---|---|
position |
string |
'bottom-right' |
'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' |
offset |
[number, number] |
[20, 20] |
[horizontal, vertical] px from edge |
size |
string |
'52px' |
Toggle button size — px, em, rem, or % |
lang |
string |
browser lang | 'en' or 'ne' |
storageKey |
string |
'ally-wgt' |
localStorage key — change to avoid collisions when multiple instances share a domain |
keyboardShortcut |
boolean |
true |
Alt+A toggles the panel. Set false to disable |
showViolationBubble |
boolean |
true |
Red badge on the toggle button showing the axe-core violation count. Set false to hide it |
icon |
string |
'default' |
Built-in variant name or a raw SVG string for the toggle button icon |
icons |
object |
— | Override any icon in the full icon set (see Icon Keys) |
poweredByText |
string |
'Ally Widget' |
Footer link label |
poweredByUrl |
string |
widget repo URL | Footer link href |
disableFeatures |
string[] |
[] |
Feature keys to hide from the panel (see Feature Keys) |
featureOverrides |
object |
{} |
Override label, icon, iconColor, or iconInnerColor per feature key (use '*' as wildcard) |
theme |
object |
— | All theme tokens nested under one key (alternative to passing tokens at the top level) |
ttsNativeVoiceName |
string |
— | Exact browser voice name to prefer for TTS |
ttsNativeVoiceLang |
string |
— | BCP-47 language tag for TTS voice selection (e.g. 'ne-NP') |
ttsProxyUrl |
string |
— | URL of your server-side TTS proxy (see Nepali TTS — Server Proxy). When set, the widget POSTs ?text=…&lang=ne to this URL instead of calling Google directly. |
ttsRate |
number |
1 |
Speech rate — 0.5 to 2 |
ttsPitch |
number |
1 |
Speech pitch — 0 to 2 |
onOpen |
function |
— | Called when the panel opens |
onClose |
function |
— | Called when the panel closes |
onFeatureToggle |
function(key, enabled) |
— | Called on every feature toggle |
onReset |
function |
— | Called when the user resets all settings |
Nepali TTS — Server Proxy
Most desktop browsers do not ship a ne-NP (Nepali) voice. When lang: 'ne' is set and no native voice is found, the widget automatically falls back to Google Translate TTS. However, Google's TTS endpoint blocks direct browser requests.
The solution is a server-side proxy: a small endpoint on your own server that forwards the request to Google with the correct headers. Set ttsProxyUrl to your proxy's path and the widget will call it instead.
AllyWidget.init({
lang: 'ne',
ttsProxyUrl: '/api/tts', // your proxy endpoint
});The widget calls: GET /api/tts?text=<encoded-text>&lang=ne
Your proxy must return the audio (audio/mpeg) directly in the response body.
See INTEGRATION.md → Nepali TTS Proxy for copy-paste implementations in Next.js, Express, PHP, Laravel, and WordPress.
Theme Customization
Pass any of these tokens directly in options, or group them under theme: { … }:
window.AllyWidgetOptions = {
// Option A — flat
primaryColor: '#e11d48',
borderRadius: '8px',
// Option B — grouped (same result)
theme: {
primaryColor: '#e11d48',
borderRadius: '8px'
}
};Theme tokens
| Token | Default | Notes |
|---|---|---|
primaryColor |
#1976d2 |
Main brand color — fallback for any unset granular key |
primaryColorLight |
#42a5f5 |
Lighter tint |
primaryColorDark |
#0d47a1 |
Darker shade |
backgroundColor |
#f5f7fa |
Panel background |
textColor |
#222222 |
Panel text |
textColorInverted |
#ffffff |
Text on primary background |
cardBackground |
#ffffff |
Feature card background |
borderColor |
#d1d5db |
Dividers and borders |
focusRingColor |
#1976d2 |
Keyboard focus ring |
hoverColor |
#42a5f5 |
Button hover border/shadow color |
activeColor |
#0d47a1 |
Legacy active color |
borderRadius |
8px |
Panel corner radius |
buttonBorderRadius |
0.4rem |
Toggle button shape |
headerHeight |
54px |
Panel header height |
focusBorderWidth |
3px |
Focus ring width |
focusOutlineOffset |
2px |
Focus ring offset |
zIndex |
100000 |
Stacking context |
buttonSize |
52px |
Alias for size |
Granular color tokens
Each element of the widget can be independently colored. Unset keys fall back to primaryColor.
Toggle button (floating launcher)
| Token | Default | Controls |
|---|---|---|
toggleButtonBg |
primaryColor |
Floating button background |
toggleButtonRingColor |
primaryColor |
Inset ring color |
toggleButtonIconColor |
#ffffff |
SVG fill on the toggle icon |
toggleButtonBorderRadius |
50% |
Button corner radius (e.g. '16px' for rounded-square) |
toggleButtonShadow |
0 4px 14px rgba(0,0,0,0.22) |
Resting drop shadow |
toggleButtonShadowHover |
0 8px 22px rgba(0,0,0,0.22) |
Shadow on hover / focus |
toggleButtonLabel |
'Accessibility' |
Text revealed on hover-expand |
toggleButtonShortcut |
'' |
Shortcut hint revealed on hover-expand (e.g. 'Ctrl+F2') |
toggleLabelFontSize |
1rem |
Font size of the hover label |
toggleLabelFontWeight |
700 |
Font weight of the hover label |
toggleShortcutFontSize |
0.7rem |
Font size of the shortcut hint |
toggleShortcutFontWeight |
600 |
Font weight of the shortcut hint |
Menu header bar
| Token | Default | Controls |
|---|---|---|
menuHeaderBg |
primaryColor |
Header background |
menuHeaderColor |
#ffffff |
Header title text and icon fill |
Feature buttons (inside the menu)
| Token | Default | Controls |
|---|---|---|
featureIconColor |
#222222 |
Default icon fill for all feature buttons |
featureIconActiveColor |
#ffffff |
Icon fill when a feature is ON |
featureButtonActiveBg |
primaryColor |
Button background when feature is ON |
featureButtonActiveBorderColor |
primaryColor |
Button border when feature is ON |
featureButtonHoverBg |
#f3f4f6 |
Button hover background |
Section headings
| Token | Default | Controls |
|---|---|---|
sectionTitleColor |
#6b7280 |
Section heading text color |
Panel sizing & scaling
| Token | Default | Controls |
|---|---|---|
menuScale |
1 |
CSS transform: scale() — scales the entire panel uniformly (text, icons, padding). transform-origin auto-set from position. E.g. '0.85' |
menuMaxWidth |
430px |
Maximum width of the panel |
menuFontSize |
16px |
Base font size of the panel |
menuBorderRadius |
16px |
Panel corner radius |
menuContentPadding |
14px |
Horizontal padding inside the panel |
menuSectionGap |
16px |
Gap between top-level sections (Speech, Text, Color…) |
menuCategoryGap |
10px |
Gap between items within a section |
sectionTitleFontSize |
14px |
Font size of section headings (Speech, Text…) |
featureButtonGap |
12px |
Gap between feature buttons in the grid |
featureButtonPadding |
10px |
Inner padding of each feature button |
featureButtonFontSize |
15px |
Base font size inside feature buttons |
featureButtonLabelSize |
13px |
Font size of feature button labels |
featureIconSize |
24px |
SVG icon size inside feature buttons |
headerIconSize |
28px |
SVG icon size in the menu header (close / back) |
Feature Keys
Use these keys with disableFeatures and featureOverrides.
Text
| Key | Label |
|---|---|
text-scale |
Font Size (slider) |
bold-text |
Font Weight |
line-spacing |
Line Height |
letter-spacing |
Letter Spacing |
readable-text |
Dyslexia Font |
Color & Contrast
| Key | Label |
|---|---|
contrast-toggle |
Contrast (cycles light → dark) |
saturation-toggle |
Saturation (cycles low → high) |
invert-colors |
Invert Colors |
Reading Aids
| Key | Label |
|---|---|
highlight-links |
Highlight Links |
highlight-title |
Highlight Titles |
reading-aid |
Reading Guide |
simple-layout |
Simplify Layout |
Interaction
| Key | Label |
|---|---|
large-pointer |
Big Cursor |
pause-motion |
Stop Animations |
hide-images |
Hide Images |
high-contrast-mode |
High Contrast |
Speech
| Key | Label |
|---|---|
text-to-speech |
Text to Speech |
Dev mode only (?ally-dev=true)
| Key | Label |
|---|---|
annotations |
Annotations (axe-core overlays) |
accessibility-report |
Accessibility Report |
Disabling Features
window.AllyWidgetOptions = {
disableFeatures: [
'hide-images',
'readable-text', // remove Dyslexia Font button
'annotations',
'accessibility-report'
]
};Feature Label, Icon & Color Overrides
Override any feature button's label, icon SVG, or icon colors — per feature key, without touching the source.
window.AllyWidgetOptions = {
featureOverrides: {
// Rename a button
'bold-text': {
label: 'Bold'
},
// Rename + swap icon SVG
'text-to-speech': {
label: 'Read Aloud',
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>'
},
// Color overrides — no SVG rewrite needed
'reading-aid': {
iconColor: '#0369a1' // main icon fill
},
'contrast-toggle': {
iconColor: '#6d28d9', // outer path color
iconInnerColor: '#c4b5fd' // inner/secondary path color
},
// Wildcard — applies to all features not individually overridden
'*': {
iconColor: '#374151'
}
}
};iconColor sets the main SVG fill. iconInnerColor targets secondary paths on compound icons (contrast, high-contrast, saturation, invert-colors). Both override featureIconColor from the theme. Per-feature values always beat the wildcard.
Icon Keys
Pass a partial icons object to swap out any icon in the set:
window.AllyWidgetOptions = {
icons: {
largePointer: '<svg>…</svg>',
pauseMotion: '<svg>…</svg>',
readingAid: '<svg>…</svg>',
textToSpeech: '<svg>…</svg>',
highContrast: '<svg>…</svg>',
simplifyLayout: '<svg>…</svg>',
boldText: '<svg>…</svg>',
lineSpacing: '<svg>…</svg>',
letterSpacing: '<svg>…</svg>',
hideImages: '<svg>…</svg>',
dyslexiaFont: '<svg>…</svg>',
highlightLinks: '<svg>…</svg>',
highlightTitle: '<svg>…</svg>',
adjustFontSize: '<svg>…</svg>',
contrast: '<svg>…</svg>',
invertColors: '<svg>…</svg>',
saturation: '<svg>…</svg>',
annotations: '<svg>…</svg>',
accessibilityReport: '<svg>…</svg>',
reset: '<svg>…</svg>',
accessibility: '<svg>…</svg>' // toggle button default icon
}
};Language
The widget ships with English (en) and Nepali (ne).
window.AllyWidgetOptions = { lang: 'ne' };The language picker in the panel footer lets users switch at runtime. The selection is persisted to localStorage.
Text to Speech
The widget uses the browser's native Web Speech API — no external service or API key required.
When TTS is enabled, click any text element on the page to hear it read aloud.
Nepali TTS — modern browsers (Chrome, Edge) include a ne-NP voice. To force a specific voice:
window.AllyWidgetOptions = {
ttsNativeVoiceLang: 'ne-NP',
ttsNativeVoiceName: 'Google नेपाली', // optional — exact voice name
ttsRate: 0.9,
ttsPitch: 1.0
};If the requested voice is unavailable the widget falls back to the browser's default voice.
Event Callbacks
window.AllyWidgetOptions = {
onOpen() {
console.log('Panel opened');
},
onClose() {
console.log('Panel closed');
},
onFeatureToggle(key, enabled) {
// key — feature key string (e.g. 'bold-text')
// enabled — boolean (true = on, false = off)
analytics.track('a11y_feature', { key, enabled });
},
onReset() {
console.log('All settings cleared');
}
};Data Attributes
You can configure the widget via attributes on the script tag instead of window.AllyWidgetOptions:
<script
src="dist/ally-widget.min.js"
data-ally-lang="ne"
data-ally-position="bottom-left"
data-ally-size="60px"
data-ally-offset="24,24"
></script>Supported attributes: data-ally-lang, data-ally-position, data-ally-offset, data-ally-size, data-ally-icon.
Framework Integration
The widget is browser-only. AllyWidget.init() returns null when called server-side, so it is always safe to import — but you must ensure it mounts after the DOM is ready.
| Stack | Approach |
|---|---|
| Plain HTML | Script tag — just works |
| React / Vite | useEffect dynamic import |
| Next.js App Router | 'use client' component + useEffect |
| Next.js Pages Router | useEffect in _app.jsx |
| Vue 3 | onMounted in App.vue |
| Nuxt 3 | .client.js plugin |
| SvelteKit | onMount in +layout.svelte |
| Astro | <script> with is:inline in Layout |
| WordPress | wp_enqueue_script in functions.php |
| Laravel / Blade | Script tag in layout |
| Angular | ngAfterViewInit in AppComponent |
| PHP (plain) | Script tag before </body> |
Plain HTML
<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>
<!-- your content -->
<script>
window.AllyWidgetOptions = {
position: 'bottom-right',
primaryColor: '#6366f1',
lang: 'en'
};
</script>
<script src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"></script>
</body>
</html>React (Vite / CRA)
// src/components/AllyWidget.jsx
import { useEffect } from 'react'
export default function AllyWidget() {
useEffect(() => {
import('ally-widget').then(({ default: AllyWidget }) => {
AllyWidget.init({ primaryColor: '#6366f1' })
})
}, [])
return null
}// src/App.jsx
import AllyWidget from './components/AllyWidget'
export default function App() {
return (
<>
<YourApp />
<AllyWidget />
</>
)
}Next.js (App Router)
// components/AllyWidget.jsx
'use client'
import { useEffect } from 'react'
export default function AllyWidget() {
useEffect(() => {
import('ally-widget').then(({ default: AllyWidget }) => {
AllyWidget.init({ primaryColor: '#6366f1', lang: 'en' })
})
}, [])
return null
}// app/layout.jsx
import AllyWidget from '@/components/AllyWidget'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<AllyWidget />
</body>
</html>
)
}Or — CDN via next/script (zero npm install):
// app/layout.jsx
import Script from 'next/script'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Script id="ally-cfg" strategy="beforeInteractive">{`
window.AllyWidgetOptions = { primaryColor: '#6366f1' };
`}</Script>
<Script
src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"
strategy="afterInteractive"
/>
</body>
</html>
)
}Next.js (Pages Router)
// pages/_app.jsx
import { useEffect } from 'react'
export default function App({ Component, pageProps }) {
useEffect(() => {
import('ally-widget').then(({ default: AllyWidget }) => {
AllyWidget.init({ primaryColor: '#6366f1' })
})
}, [])
return <Component {...pageProps} />
}Vue 3 (Vite)
// src/App.vue
<script setup>
import { onMounted } from 'vue'
onMounted(async () => {
const { default: AllyWidget } = await import('ally-widget')
AllyWidget.init({ primaryColor: '#6366f1' })
})
</script>Nuxt 3
// plugins/ally-widget.client.js
// The .client suffix tells Nuxt this is browser-only — no SSR guard needed
import AllyWidget from 'ally-widget'
export default defineNuxtPlugin(() => {
AllyWidget.init({ primaryColor: '#6366f1' })
})SvelteKit
<!-- src/routes/+layout.svelte -->
<script>
import { onMount } from 'svelte'
onMount(async () => {
const { default: AllyWidget } = await import('ally-widget')
AllyWidget.init({ primaryColor: '#6366f1' })
})
</script>
<slot />Astro
<!-- src/layouts/Layout.astro -->
---
// server-side — nothing here
---
<html lang="en">
<head>...</head>
<body>
<slot />
<script>
// This block runs in the browser only
import AllyWidget from 'ally-widget'
AllyWidget.init({ primaryColor: '#6366f1' })
</script>
</body>
</html>
WordPress
// functions.php (child theme or plugin)
function enqueue_ally_widget() {
wp_enqueue_script(
'ally-widget',
'https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js',
[],
'1.0.0',
true // load in footer
);
wp_add_inline_script(
'ally-widget',
"window.AllyWidgetOptions = {
position: 'bottom-right',
primaryColor: '#6366f1',
poweredByText: '" . get_bloginfo('name') . "'
};",
'before'
);
}
add_action('wp_enqueue_scripts', 'enqueue_ally_widget');
Laravel / Blade
{{-- resources/views/layouts/app.blade.php --}}
<script>
window.AllyWidgetOptions = {
position: 'bottom-right',
primaryColor: '#6366f1',
poweredByText: '{{ config("app.name") }}'
};
</script>
<script src="{{ asset('vendor/ally-widget/ally-widget.min.js') }}"></script>
{{-- or CDN: src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js" --}}Angular
// src/app/app.component.ts
import { Component, AfterViewInit } from '@angular/core'
@Component({ selector: 'app-root', templateUrl: './app.component.html' })
export class AppComponent implements AfterViewInit {
ngAfterViewInit() {
import('ally-widget').then(({ default: AllyWidget }) => {
AllyWidget.init({ primaryColor: '#6366f1' })
})
}
}PHP (plain)
<!-- footer.php or before </body> -->
<script>
window.AllyWidgetOptions = {
position: 'bottom-right',
primaryColor: '#6366f1'
};
</script>
<script src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"></script>
AllyWidget.init() reference
const instance = AllyWidget.init(options)
// Returns the widget instance, or null when called server-side.
// Handles DOMContentLoaded automatically — safe to call at any time.Disabling auto-init
Suppress the automatic CDN init when you are calling AllyWidget.init() yourself:
<script>
window.AllyWidgetOptions = { autoInit: false };
</script>Customization Guide
Minimal setup — just the essentials
Hide features your site doesn't need to keep the panel focused:
AllyWidget.init({
disableFeatures: [
'readable-text', // remove Dyslexia Font
'hide-images', // remove Hide Images
'annotations', // hide dev tools in prod
'accessibility-report'
]
})Custom colour theme
Pass any CSS value. Use primaryColor as a single fallback, or override each element independently with the granular tokens:
AllyWidget.init({
// Single fallback — all granular keys derive from this if not set
primaryColor: '#e11d48',
// ── Toggle button ────────────────────────────────────────
toggleButtonBg: '#0f172a', // dark button
toggleButtonRingColor:'#e11d48', // red ring
toggleButtonIconColor:'#f8fafc', // near-white icon
// ── Menu header ──────────────────────────────────────────
menuHeaderBg: '#1e293b', // dark header
menuHeaderColor: '#ffffff',
// ── Feature buttons ──────────────────────────────────────
featureIconColor: '#374151', // default icon fill
featureIconActiveColor: '#ffffff', // icon color when ON
featureButtonActiveBg: '#e11d48', // active button bg
featureButtonHoverBg: '#f1f5f9', // hover bg
// ── Section titles ───────────────────────────────────────
sectionTitleColor: '#9ca3af',
// ── Panel ────────────────────────────────────────────────
backgroundColor: '#0f172a',
cardBackground: '#1e293b',
borderColor: '#334155',
borderRadius: '8px',
buttonBorderRadius: '12px',
zIndex: 99999
})Expanding FAB toggle button
The toggle button expands on hover to reveal a label and optional keyboard shortcut. All aspects are configurable:
AllyWidget.init({
size: '58px',
toggleButtonBg: '#C5161D', // button background
toggleButtonIconColor: '#ffffff', // icon color
toggleButtonBorderRadius:'16px', // rounded-square (use '50%' for circle)
toggleButtonShadow: '0 4px 14px rgba(0,0,0,0.25)',
toggleButtonShadowHover: '0 8px 22px rgba(0,0,0,0.28)',
// Text revealed on hover
toggleButtonLabel: 'Accessibility',
toggleButtonShortcut: 'Ctrl+F2', // omit or set '' to hide
toggleLabelFontSize: '1.1rem',
toggleLabelFontWeight: '700',
toggleShortcutFontSize: '0.7rem',
toggleShortcutFontWeight:'600',
})The button animates from its square size to roughly 3× its width on hover, then snaps back when the menu is open.
Custom branding
Replace the footer link with your own, or pass an empty string to hide it entirely:
AllyWidget.init({
poweredByText: 'My Company', // shown in the menu footer
poweredByUrl: 'https://mycompany.com'
})
// Hide the branding link completely:
AllyWidget.init({ poweredByText: '' })Rename, re-icon, or recolor any button
AllyWidget.init({
featureOverrides: {
// Rename
'text-to-speech': { label: 'Read Page Aloud' },
'reading-aid': { label: 'Focus Line' },
// Rename + custom SVG
'bold-text': {
label: 'Bold',
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>'
},
// Color only — no SVG required
'contrast-toggle': { iconColor: '#6d28d9', iconInnerColor: '#c4b5fd' },
'highlight-links': { iconColor: '#0369a1' },
// Wildcard — all others
'*': { iconColor: '#374151' }
}
})Custom toggle button icon
AllyWidget.init({
// Built-in variants: 'default' | 'person' | 'eye' | 'universal'
icon: 'universal',
// Or pass any SVG string:
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>'
})Position and size
AllyWidget.init({
position: 'bottom-left', // bottom-right | bottom-left | top-right | top-left
offset: [24, 32], // [horizontal, vertical] px from edge
size: '60px' // toggle button size
})Event callbacks for analytics
AllyWidget.init({
onOpen() { analytics.track('ally_open') },
onClose() { analytics.track('ally_close') },
onFeatureToggle(key, enabled) {
analytics.track('ally_feature', { key, enabled })
// key examples: 'bold-text', 'contrast-toggle', 'text-to-speech'
},
onReset() { analytics.track('ally_reset') }
})Full example — everything configured
window.AllyWidgetOptions = {
// Placement
position: 'bottom-right',
offset: [20, 20],
size: '56px',
keyboardShortcut: true, // Alt+A
// Language
lang: 'ne', // 'en' | 'ne'
// Storage
storageKey: 'myapp-ally',
// Branding
poweredByText: 'My Company',
poweredByUrl: 'https://mycompany.com',
// Theme — base
primaryColor: '#6366f1',
borderRadius: '12px',
buttonBorderRadius: '50%',
zIndex: 9999,
// Theme — toggle button FAB
toggleButtonBg: '#6366f1',
toggleButtonIconColor: '#ffffff',
toggleButtonBorderRadius: '50%',
toggleButtonShadow: '0 4px 14px rgba(0,0,0,0.22)',
toggleButtonShadowHover: '0 8px 22px rgba(0,0,0,0.22)',
toggleButtonLabel: 'Accessibility',
toggleButtonShortcut: 'Ctrl+F2',
toggleLabelFontSize: '1rem',
toggleLabelFontWeight: '700',
toggleShortcutFontSize: '0.7rem',
toggleShortcutFontWeight: '600',
// Theme — menu
menuHeaderBg: '#4f46e5',
featureIconColor: '#374151',
featureIconActiveColor: '#ffffff',
featureButtonActiveBg: '#6366f1',
sectionTitleColor: '#6b7280',
// Features
disableFeatures: ['annotations', 'accessibility-report'],
featureOverrides: {
'text-to-speech': { label: 'Read Aloud' },
'*': { iconColor: '#374151' },
'contrast-toggle':{ iconColor: '#6366f1', iconInnerColor: '#a5b4fc' }
},
// TTS
ttsNativeVoiceLang: 'ne-NP',
ttsRate: 0.9,
ttsPitch: 1.0,
// Callbacks
onOpen() { console.log('opened') },
onClose() { console.log('closed') },
onFeatureToggle(key, enabled) { console.log(key, enabled) },
onReset() { console.log('reset') }
}Dev Mode
Append ?ally-dev=true to any URL to enable the Annotations and Accessibility Report tools. These run an axe-core scan and overlay issue markers on the page — useful during development, hidden in production.
https://yoursite.com/page?ally-dev=true
The axe-core library is loaded on demand in dev mode only — zero weight in production.
Hiding the violation badge
When violations are found, a red number badge appears on the toggle button. To hide it:
AllyWidget.init({
showViolationBubble: false
});Keyboard Shortcut
Alt + A toggles the panel on any page. Disable it with keyboardShortcut: false.
Publishing to npm
# Set your token
export NPM_TOKEN=your_token_here # or put it in .env
# Build then publish
npm run build
npm publishThe .npmrc at the project root reads NPM_TOKEN automatically:
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
License
MIT — see LICENSE