a11y-widget-v1
v1.0.0
Published
Accessibility Widget v1 - WCAG 2.1 AA-aligned support layer
Maintainers
Readme
Accessibility Widget v1 — Embed Anywhere
A lightweight, embeddable accessibility widget that provides WCAG 2.1 AA–aligned enhancements for the widget surface + any canonical-rendered content you control, without claiming "full ADA compliance" for the host site.
Overview
This widget is a support layer + render-time accessibility controls — not an overlay that "fixes the whole site." It provides:
- ✅ Accessibility enhancements for widget UI itself
- ✅ CSS-based user preference transforms (contrast, text size, spacing)
- ✅ Keyboard and screen reader support
- ✅ Global keyboard shortcut (Alt+A) to quickly open widget
- ✅ Preference persistence (localStorage/cookie)
- ✅ Optional profile presets (dyslexia-friendly, low-vision, reduced motion)
- ✅ Text-to-Speech: Read selected text or full page aloud with customizable voice settings (including PDF text extraction when available)
- ✅ Translation: Automatically translate page content into multiple languages (translates all text on declared surfaces)
- ✅ Reading Aids: Reading ruler, screen mask, text-only mode, adjustable margins
- ✅ Focus Tools: Customizable cursor size, page magnifier
- ✅ Dictionary: Double-click words to see definitions
- ✅ Inline Tool Management: Use the widget's
Toolsbutton to reorder tools and hide/show unused controls - ✅ Support + Monitoring: Optional backend endpoints for telemetry, install heartbeats, widget errors, support cases, and translation
Scope boundaries: This widget only affects elements you explicitly declare as surfaces. It does not fix host-site HTML outside those surfaces, third-party embeds, or guarantee full-site compliance. PDF text extraction is attempted for same-origin PDFs but may be limited by CORS restrictions.
Quick Start
Installation Options
Choose one of two installation methods:
Option 1: CDN (Recommended for Simple Sites)
Just add this single line to your HTML:
<!-- Use versioned tag to ensure you get the latest stable version -->
<script src="https://cdn.jsdelivr.net/gh/braieswabe/[email protected]/a11y-widget-loader-v1.7.2.js" defer></script>Note: We use version tags (for example @v1.7.2) instead of branch names (@main) because jsDelivr CDN aggressively caches branch URLs for up to 7 days. Version tags are served immediately and ensure you always get the exact version you specify.
That's it! The widget loads automatically from GitHub. No configuration needed.
Option 2: NPM (Recommended for Bundled Apps)
Install via npm:
npm install @careerdriver/a11y-widgetThen import and initialize in your code:
import { initA11yWidget } from "@careerdriver/a11y-widget";
import "@careerdriver/a11y-widget/styles.css";
initA11yWidget({
siteId: "example.com",
position: "right"
});✨ Version Updates:
- CDN: Update the version tag in your script tag (e.g.,
@v1.6.10→@v1.7.2) - NPM: Run
npm update @careerdriver/a11y-widgetto get the latest version
⌨️ Keyboard Shortcut: Press Alt+A (Option+A on Mac) from anywhere on the page to quickly open/close the accessibility widget. The shortcut doesn't interfere with typing in input fields.
Optional Configuration
CDN Method
If you want to configure surfaces, telemetry, monitoring, or support routing, add configuration before the loader script:
<script>
window.__A11Y_WIDGET__ = {
siteId: "example.com",
apiKey: "YOUR_CLIENT_API_KEY",
position: "right", // Optional: "left" or "right"
keyboardShortcut: "Alt+A", // Optional: "Alt+A", "Ctrl+Alt+A", or null to disable
globalMode: false, // Optional: If true, applies transformations to entire website (fonts, colors, sizes)
surfaces: ["body", "main"], // Optional: CSS selectors (ignored if globalMode is true)
telemetryEndpoint: "https://your-widget-backend.com/api/telemetry"
};
</script>
<script src="https://cdn.jsdelivr.net/gh/braieswabe/[email protected]/a11y-widget-loader-v1.7.2.js" defer></script>NPM Method
Pass configuration directly to initA11yWidget():
import { initA11yWidget } from "@careerdriver/a11y-widget";
import "@careerdriver/a11y-widget/styles.css";
initA11yWidget({
siteId: "example.com",
apiKey: "YOUR_CLIENT_API_KEY",
position: "right",
keyboardShortcut: "Alt+A",
globalMode: false,
surfaces: ["body", "main"],
telemetryEndpoint: "https://your-widget-backend.com/api/telemetry"
});Global Mode: When enabled, the widget applies transformations (fonts, font sizes, colors, spacing) to the entire website, completely overhauling the user interface. When disabled (default), transformations only apply to declared surfaces.
Tool Management: Visitors can click Tools inside the widget header to show inline up/down and visibility controls beside each tool name. Order and hidden tools persist per visitor.
Monitoring: When telemetryEndpoint is set, the widget also derives /api/widget/heartbeat, /api/widget/errors, /api/support/cases, and /api/translate from the same backend unless you override those endpoints directly.
Authorized Monitoring Installation
The one-line install can run the widget UI without backend monitoring. To use database-backed heartbeat tracking, widget error logging, support cases, or translation, the site must be authorized by the backend first.
- Log in to the employee/admin dashboard.
- Create or select the client record and copy its API key.
- Add every production domain where the widget will run, without protocol, for example
example.comandwww.example.com. - Use the same
siteIdin the website snippet that is assigned to that client. - Add the client API key as
apiKeyorlicenseKeyin the widget config. - Set
telemetryEndpointto your backend/api/telemetry. The widget will derive heartbeat, error, support, and translation endpoints from the same backend unless they are overridden.
Development origins such as localhost and 127.0.0.1 are allowed for logging endpoints so local demos can submit support cases and heartbeats. Production domains still require a registered domain match or a valid API/license key.
Custom Button Control
To hide the default button and control it with your own header button, see Custom Button Control Guide.
Configuration Options
CDN Method: Configuration is via window.__A11Y_WIDGET__ object or data attributes (set before script loads).
NPM Method: Configuration is passed directly to initA11yWidget(config) function.
Both methods support the same configuration options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| siteId | string | null | Auto-detected from hostname if not provided |
| apiKey | string | null | Client API key used by protected backend endpoints |
| licenseKey | string | null | Alias for backend authorization when using license-key terminology |
| position | "left"\|"right" | "right" | Widget position on screen |
| keyboardShortcut | string|null | "Alt+A" | Global keyboard shortcut to open/close widget (e.g., "Alt+A", "Ctrl+Alt+A", or null to disable) |
| globalMode | boolean | false | If true, applies transformations to entire website (fonts, colors, sizes). When enabled, surfaces is ignored. |
| surfaces | string[] | ["body"] | CSS selectors to mark as data-a11y-surface="true" (ignored if globalMode is true) |
| enableTelemetry | boolean | false | Enable telemetry events |
| telemetryEndpoint | string | null | Backend endpoint for telemetry (e.g., /api/telemetry) |
| heartbeatEndpoint | string | Derived from telemetryEndpoint | Backend endpoint for widget installation heartbeats |
| errorEndpoint | string | Derived from telemetryEndpoint | Backend endpoint for widget runtime errors |
| supportEndpoint | string | Derived from telemetryEndpoint | Backend endpoint for visitor support cases |
| translateEndpoint | string | Derived from telemetryEndpoint | Backend endpoint for server-side translation |
| zIndex | number | 2147483000 | Widget z-index |
| initialOpen | boolean | false | Open widget on page load |
| locale | string | "en" | Locale (future use) |
| features | object | See below | Toggle individual features |
Feature Flags
features: {
contrast: true, // Contrast modes (default/high/dark/light)
fontScale: true, // Text size slider (100-160%)
spacing: true, // Text spacing presets (normal/comfortable/max)
reduceMotion: true, // Reduce motion toggle
readableFont: true, // Readable font toggle
presets: true, // Preset buttons (low vision, dyslexia, motion)
reset: true, // Reset button
skipLink: true // Skip to content link
}Platform-Specific Installation Guides
- Static HTML / Custom Sites — Basic HTML sites
- React SPA — Create React App, Vite, etc.
- Next.js — Next.js App Router or Pages Router
- WordPress — WordPress themes/plugins
- Shopify — Shopify themes
- Google Tag Manager — GTM implementation
- Django/Rails/.NET — Server-side rendered apps
- Custom Button Control — Hide default button, use your own header button
How It Works
Surface Scoping vs Global Mode
By Default (Surface Mode): The widget only applies transforms to elements you declare in surfaces. Those elements get data-a11y-surface="true":
<!-- Your HTML -->
<body>
<main data-canonical-surface="true">
<p>This content will be transformed</p>
</main>
<aside>
<p>This content will NOT be transformed</p>
</aside>
</body>// Widget config
surfaces: ["body", "[data-canonical-surface='true']"]Global Mode: When globalMode: true is set, the widget applies transformations to the entire website, completely overhauling fonts, font sizes, background colors, and other UI elements. This replaces existing website styles globally:
// Widget config
globalMode: true // Applies to entire website, not just surfacesNote: When global mode is enabled, the surfaces configuration is ignored - all elements are transformed.
Preference Persistence
User preferences are stored per domain in:
localStorage(primary)- Cookies (fallback if localStorage unavailable)
Storage key: __a11yWidgetPrefs__
Translation Behavior
When translation is enabled:
- All text content within declared surfaces (
[data-a11y-surface="true"]) is automatically translated - Original text is preserved and restored when translation is disabled
- Language changes trigger automatic re-translation
- Translations are cached to improve performance
- Translation API rate limits apply (MyMemory API free tier)
Telemetry (Optional)
If enableTelemetry: true and telemetryEndpoint is set, the widget sends events to your backend:
widget_open— Widget panel openedsetting_change— User changed a settingreset— User reset preferenceswidget_close— Widget panel closed
Events include: siteId, event, payload, url, userAgent (no PII).
Protected backend features use the Neon-backed validation tables. A production request to heartbeat, widget errors, support cases, or translation must come from a registered domain or include a valid apiKey/licenseKey. Localhost and 127.0.0.1 are allowed for development logging tests.
API Reference
Telemetry Endpoint
POST /api/telemetry
{
"siteId": "your-site-id",
"event": "widget_open",
"payload": {},
"url": "https://example.com/page",
"userAgent": "Mozilla/5.0..."
}Monitoring Endpoints
These endpoints are derived automatically from telemetryEndpoint unless configured directly:
- POST
/api/widget/heartbeat— Creates or updates the installed-site record with domain, URL, title, favicon, version, browser metadata, and last seen time. - POST
/api/widget/errors— Stores widget runtime/API failures for Employee Monitoring. - POST
/api/support/cases— Stores visitor support cases from the widget Support button. - POST
/api/translate— Runs server-side translation and cache lookup when configured.
Production requests are authorized by allowed domain, client API key, or license key. Make sure NEON_DATABASE_URL is configured on the backend and /api/health reports database: connected.
Config Endpoint (Optional)
GET /api/config/[siteId]
Returns site-specific configuration JSON.
Health Check
GET /api/health
Returns: {"status":"ok","database":"connected"}
See DEPLOYMENT.md for backend setup.
Files
a11y-widget.js— Main widget (IIFE, no dependencies)a11y-widget.css— Widget styles + surface transformssupport-statement.md— Public support statement (scope boundaries)wcag-matrix.md— WCAG 2.1 AA coverage matrixDEPLOYMENT.md— Deployment guide (Vercel + Neon)
Support Statement
This widget provides accessibility enhancements aligned with WCAG 2.1 AA for supported surfaces only.
What we cover:
- Widget UI accessibility
- Declared content surfaces
- User preference controls
What we don't cover:
- Host-site HTML outside declared surfaces
- Third-party embeds (maps, iframes, booking engines)
- PDFs or downloads
- Full-site ADA compliance guarantee
See support-statement.md for full details.
WCAG Coverage
For widget UI + declared surfaces, we cover:
- ✅ 1.4.3 Contrast (Minimum) — Widget UI + contrast modes
- ✅ 1.4.4 Resize text — Font scaling 100-160%
- ✅ 1.4.10 Reflow — Responsive at 320px width
- ✅ 1.4.12 Text spacing — Spacing presets
- ✅ 2.1.1 Keyboard — Full keyboard operation
- ✅ 2.4.1 Bypass blocks — Skip link
- ✅ 2.4.7 Focus visible — Strong focus indicators
- ✅ 3.2.3 Consistent navigation — Consistent widget UI
- ✅ 3.3.2 Labels/instructions — All controls labeled
- ✅ 4.1.2 Name, role, value — Correct ARIA roles/states
See wcag-matrix.md for detailed coverage matrix.
Troubleshooting
Widget Not Appearing
- Check browser console for errors
- Verify script loads:
curl https://yourdomain.com/a11y-widget/v1/a11y-widget.js - Check CSP allows external scripts/styles
- Verify
surfacesselectors match your HTML
Settings Not Persisting
- Check browser localStorage (DevTools → Application → Local Storage)
- Verify cookies aren't blocked
- Check storage key:
__a11yWidgetPrefs__
CSP Issues
If CSP blocks inline scripts, use data attributes:
<script
src="https://cdn.YOURDOMAIN.com/a11y-widget/v1/a11y-widget.js"
data-site-id="YOUR_SITE_ID"
data-position="right"
defer
></script>Telemetry Not Sending
- Verify
enableTelemetry: true - Check
telemetryEndpointis set correctly - Check browser network tab for POST requests
- Verify backend endpoint is accessible
- Check CORS headers allow your domain
Backend Request Returns 403
- Add the deployed domain in the employee/admin dashboard without protocol, for example
example.com. - Confirm the snippet uses the assigned
siteId. - Include the client
apiKeyorlicenseKeyif the domain is not already allowed. - Confirm
telemetryEndpointpoints to the correct backend/api/telemetryURL. - Confirm
/api/healthreturnsdatabase: connected.
Browser Support
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)
Requires: ES5+ JavaScript, CSS Custom Properties (CSS Variables)
License
MIT License — See LICENSE file
Contributing
See docs/DEVELOPER.md for development guidelines.
Deployment
See DEPLOYMENT.md for Vercel + Neon setup instructions.
