@bauer-group/accessibility-widget
v1.4.2
Published
Lazy-loading Accessibility-Widget nach BFSG/EN 301 549/WCAG 2.2 AA. Loader ~4 KB gzip, Core ~14 KB gzip. Kein Tracking, kein Cookie, kein DOM-/ARIA-Override.
Maintainers
Readme
@bauer-group/accessibility-widget
Lazy-loading accessibility widget for BFSG · EN 301 549 · WCAG 2.2 AA. Loader ~5.7 KB gzip · Core ~26 KB gzip (28 locales embedded). No tracking, no cookies, no DOM/ARIA overrides of the host page.
🇬🇧 English · 🇩🇪 Deutsch
One-line integration
<!-- Latest within a major — auto patch/minor updates (no SRI pin): -->
<script
src="https://widgets.professional-hosting.com/accessibility-widget/v1/accessibility-widget-loader.min.js"
defer
></script>That's enough in 95 % of cases. The widget auto-detects the language, renders the FAB in the bottom-right corner, and persists all user preferences in localStorage.
For production, pin an immutable version and secure it with Subresource Integrity:
<script
src="https://widgets.professional-hosting.com/accessibility-widget/1.0.5/accessibility-widget-loader.min.js"
integrity="sha384-…"
crossorigin="anonymous"
defer
></script>The …/<version>/… paths never change (SRI-safe to pin); the …/v<major>/… alias always serves the latest release in that major. Per-release SRI hashes live at …/<version>/integrity.json (and in dist/integrity.txt / dist/integrity.json).
Configuration (optional)
When the defaults don't fit — branding, position, custom assets, feature-gating, statement link — set a config object before the loader script:
<script>
window.AccessibilityWidgetConfig = {
locale: 'auto',
primaryColor: '#0058a3',
statementUrl: '/accessibility-statement',
};
</script>
<script src="/accessibility-widget-loader.min.js" defer></script>Invalid values fall back to defaults silently. With debug: true, warnings appear in the console.
Full config reference
All fields are optional. TypeScript type definitions ship with @bauer-group/accessibility-widget (type-import WidgetConfig).
Asset loading
| Field | Type | Default | Purpose |
| --------------- | ---------------- | -------------------------------------------------------- | ------------------------------------------------------------ |
| corePath | string | /accessibility-widget/accessibility-widget-core.min.js | URL of the on-demand core bundle |
| cssPath | string | /accessibility-widget/accessibility-widget.min.css | URL of the widget CSS |
| coreIntegrity | string \| null | null | SRI hash for the core (sha384-…, see dist/integrity.txt) |
| cssIntegrity | string \| null | null | SRI hash for the CSS |
Localization
| Field | Type | Default | Purpose |
| -------- | ------------------ | -------- | -------------------------------------------------------------------------------------------------------- |
| locale | Locale \| 'auto' | 'auto' | Preselected language. 'auto' reads <html lang> / navigator.language. 28 locales supported (below). |
de, en, fr, es, it, pl, tr, ar, zh, hi, pt, bn, ru, ja, ko, vi, fa, ur, th, id, he, nl, sv, cs, el, hu, ro, uk.
RTL: ar, fa, ur, he.
UI / branding
| Field | Type | Default | Purpose |
| -------------- | -------------------------------------------------------------- | ------------------ | --------------------------------------------------------------------------------------- |
| position | 'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' | 'bottom-right' | FAB anchor corner |
| offset | { x?: number; y?: number } | { x: 20, y: 20 } | Pixel offset from the anchor corner. Useful when a chat widget / cookie banner collides |
| zIndex | number | 2147483646 | FAB z-index. Lower it so in-page dialogs stack above |
| primaryColor | string | #0058a3 | FAB background (any valid CSS color: hex, rgb(), hsl(), oklch(), named) |
| buttonLabel | string \| null | null | Overrides the FAB aria-label. null → localized default |
Persistence
| Field | Type | Default | Purpose |
| ------------ | -------- | ---------------------- | -------------------------------------------------------------------------------------------------- |
| storageKey | string | accessibility-widget | localStorage key for user preferences. Set differently on multi-tenant platforms to isolate brands |
First-visit experience
| Field | Type | Default | Purpose |
| ----------------- | ------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| initialFeatures | Partial<Record<FeatureId, boolean>> | {} | Features enabled on the first visit (no persisted state). Seeded once into localStorage — after that, the persisted state wins |
Feature-gating
| Field | Type | Default | Purpose |
| ------------------ | ------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
| disabledFeatures | FeatureId[] | [] | Features hidden from the panel and not activatable via a profile preset. e.g. ['tts'] on pages without text content |
Legal / compliance
| Field | Type | Default | Purpose |
| -------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| statementUrl | string | — | URL to the accessibility statement (panel footer link). Recommended for BFSG § 14 / EN 301 549 § 12.1.1. Only http(s) + relative URLs; javascript: / data: are blocked |
Behavior
| Field | Type | Default | Purpose |
| ---------------------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| draggableFab | boolean | false | End users can move the FAB via mouse/touch/Shift+Arrow. Position persists in storageKey and is reapplied next visit. Panel reset clears it. |
| respectReducedMotion | boolean | true | Respect prefers-reduced-motion for animation features |
| hideOnPrint | boolean | true | Hide the FAB in print media |
| debug | boolean | false | Emit silent failures (localStorage quota, load errors, validation) as console.warn |
Example: enterprise deployment
<script>
window.AccessibilityWidgetConfig = {
// Self-hosted assets with SRI pinning
corePath: '/assets/accessibility-widget-core.min.js',
cssPath: '/assets/accessibility-widget.min.css',
coreIntegrity: 'sha384-BEDPwzuDcF3GgBUbNzaOTpqKHhONBEr73ZCZLY0Kg3o8opuY7JDEdj/25LiIbM3A',
cssIntegrity: 'sha384-0t+ii2SnsYoSD/YuByxjc1EjkQOThsb5Z6ZqARi0WLkki39il/sfHcI6OzfqWrW9',
// Brand
locale: 'de',
primaryColor: '#0058a3',
position: 'bottom-left',
offset: { x: 24, y: 96 }, // room for a live chat on top
zIndex: 9999, // below in-page dialogs
// Isolated storage namespace on a multi-tenant platform
storageKey: 'acme-prod-a11y',
// Onboarding: focus ring for first-time visitors
initialFeatures: { focusOutline: true },
// Don't offer TTS on the corporate site (too little text content)
disabledFeatures: ['tts'],
// Compliance
statementUrl: '/accessibility-statement',
debug: false,
};
</script>
<script src="/assets/accessibility-widget-loader.min.js" defer></script>Features
The widget provides user-preference features — it never alters the host page's ARIA/semantics. Everything runs via data-aw-* attributes on <html> and CSS filters.
| Feature | Effect |
| ------------------------------------------- | ------------------------------------- |
| fontSize | 1 → 1.2 → 1.4 → 1.6 text scaling |
| lineHeight | 1.5 → 1.8 → 2.0 |
| letterSpacing | 0 → 0.05 → 0.1em |
| contrast | off / high / dark / inverted |
| grayscale, invertColors, dyslexiaFont | filters / font overrides |
| highlightLinks | underline, outline, yellow background |
| pauseAnimations | animation: none !important |
| bigCursor, focusOutline | visibility aids |
| readingMask, readingGuide | reading aids |
| tts | Web Speech API, local TTS per locale |
| structureNav | generated headings navigation |
Profiles
Presets activate several features at once: visionImpaired, motor, cognitive, seizureSafe, adhd, blind.
(Features disabled via disabledFeatures are not activated by profiles.)
Runtime API
| Method | Signature | Purpose |
| ------------------- | --------------------- | ------------------------------------------------------------------ |
| open(opts?) | Promise<void> | Open the panel (loads the core on demand) |
| close() | void | Close the panel |
| reset() | void | Clear all preferences + reload the page |
| set(id, value) | Promise<void> | Toggle a single feature |
| applyProfile(id) | Promise<boolean> | Apply a profile preset (6 profiles, see above) |
| setLocale(locale) | Promise<boolean> | Switch language at runtime (persistent, panel re-renders live) |
| setPosition(pos) | void | Set the FAB to { x, y } or back to the config anchor with null |
| getState() | WidgetState \| null | Read the persisted state synchronously |
| on(event, cb) | () => void | Subscribe to an event, returns an unsubscribe function |
Examples
// Toggle a feature
window.AccessibilityWidget.set('fontSize', true);
// Apply a profile
await window.AccessibilityWidget.applyProfile('visionImpaired');
// Switch language (no reload, panel re-renders)
await window.AccessibilityWidget.setLocale('fr');
// Position the FAB programmatically
window.AccessibilityWidget.setPosition({ x: 40, y: 200 });
window.AccessibilityWidget.setPosition(null); // back to the config anchor
// Subscribe to events (e.g. privacy-friendly analytics)
const off = window.AccessibilityWidget.on('stateChange', ({ state }) => {
console.log(
'active:',
Object.keys(state.features).filter((k) => state.features[k]),
);
});
off(); // unsubscribe later
// Or natively, without the helper
document.addEventListener('accessibility-widget:profileApplied', (e) => {
analytics.track('a11y_profile', e.detail.profile);
});Events
| Event | Payload | Fires on |
| ---------------- | -------------------------------------------- | ---------------------------------------------------- |
| stateChange | { state: WidgetState } | every state change (feature, profile, locale, reset) |
| open | { trigger: HTMLElement \| null } | the panel opens |
| close | {} | the panel closes |
| profileApplied | { profile: ProfileId; state: WidgetState } | a profile preset is applied |
| localeChanged | { locale: Locale } | the locale changes |
| reset | {} | all preferences are cleared |
Architecture
loader.ts— IIFE, ~5.7 KB gzip, injects critical CSS, renders the FAB, loads the core on clickcore.ts— panel UI, features, focus trap, TTS; loaded on demand onlyconfig.ts— config resolution with runtime validation (resolveConfig)state.ts— localStorage persistence with debug-aware catchesfeatures/*— one file per feature, tree-shakeablepanel/panel.ts— pure DOM composition, no framework dependency
Development
pnpm install
pnpm --filter @bauer-group/accessibility-widget build # builds dist/
pnpm --filter @bauer-group/accessibility-widget size # checks the bundle budget
pnpm --filter @bauer-group/accessibility-widget test # VitestLicense
@bauer-group/accessibility-widget is dual-licensed:
- GNU AGPL-3.0-only for open-source use — see LICENSE.
- Commercial license for closed/proprietary products or without the AGPL disclosure obligations —
[email protected].
Details: LICENSING.md.
🇩🇪 Deutsch
Lazy-loading Accessibility-Widget nach BFSG · EN 301 549 · WCAG 2.2 AA. Loader ~5,7 KB gzip · Core ~26 KB gzip (28 Locales eingebettet). Kein Tracking, kein Cookie, kein DOM-/ARIA-Override der Host-Seite.
🇬🇧 English · 🇩🇪 Deutsch
1-Zeilen-Integration
<!-- Neueste Version eines Majors — automatische Patch/Minor-Updates (ohne SRI): -->
<script
src="https://widgets.professional-hosting.com/accessibility-widget/v1/accessibility-widget-loader.min.js"
defer
></script>Das reicht in 95 % der Fälle. Das Widget erkennt die Sprache automatisch, rendert den FAB unten rechts und persistiert alle User-Präferenzen im localStorage.
Für Produktion eine unveränderliche Version pinnen und per Subresource Integrity absichern:
<script
src="https://widgets.professional-hosting.com/accessibility-widget/1.0.5/accessibility-widget-loader.min.js"
integrity="sha384-…"
crossorigin="anonymous"
defer
></script>Die …/<version>/…-Pfade ändern sich nie (SRI-sicher zu pinnen); der …/v<major>/…-Alias liefert stets das neueste Release dieses Majors. SRI-Hashes je Release liegen unter …/<version>/integrity.json (sowie in dist/integrity.txt / dist/integrity.json).
Konfiguration (optional)
Wenn Standardwerte nicht passen — Branding, Position, abweichende Assets, Feature-Gating, Statement-Link — setzt du vor dem Loader-Script ein Config-Objekt:
<script>
window.AccessibilityWidgetConfig = {
locale: 'auto',
primaryColor: '#0058a3',
statementUrl: '/barrierefreiheit',
};
</script>
<script src="/accessibility-widget-loader.min.js" defer></script>Ungültige Werte fallen stillschweigend auf Defaults zurück. Mit debug: true erscheinen Warnungen in der Console.
Vollständige Config-Referenz
Alle Felder sind optional. TypeScript-Typdefinitionen liegen in @bauer-group/accessibility-widget (Type-Import aus WidgetConfig).
Asset-Loading
| Feld | Typ | Default | Zweck |
| --------------- | ---------------- | -------------------------------------------------------- | ---------------------------------------------------------- |
| corePath | string | /accessibility-widget/accessibility-widget-core.min.js | URL des On-Demand-Core-Bundles |
| cssPath | string | /accessibility-widget/accessibility-widget.min.css | URL der Widget-CSS |
| coreIntegrity | string \| null | null | SRI-Hash für Core (sha384-…, siehe dist/integrity.txt) |
| cssIntegrity | string \| null | null | SRI-Hash für CSS |
Lokalisierung
| Feld | Typ | Default | Zweck |
| -------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------ |
| locale | Locale \| 'auto' | 'auto' | Vorausgewählte Sprache. 'auto' liest aus <html lang> bzw. navigator.language. 28 Locales (siehe oben). |
UI / Branding
| Feld | Typ | Default | Zweck |
| -------------- | -------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------- |
| position | 'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' | 'bottom-right' | FAB-Ankerecke |
| offset | { x?: number; y?: number } | { x: 20, y: 20 } | Pixel-Abstand zur Ankerecke. Nützlich, wenn Chat-Widget / Cookie-Banner kollidieren |
| zIndex | number | 2147483646 | FAB-z-index. Senken, damit In-Page-Dialoge drüber stacken |
| primaryColor | string | #0058a3 | FAB-Hintergrund (jeder gültige CSS-Farbwert: Hex, rgb(), hsl(), oklch(), Named) |
| buttonLabel | string \| null | null | Überschreibt FAB-aria-label. null → lokalisierter Default |
Persistenz
| Feld | Typ | Default | Zweck |
| ------------ | -------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| storageKey | string | accessibility-widget | localStorage-Key für User-Präferenzen. Auf Multi-Tenant-Plattformen abweichend setzen, um Brands zu isolieren |
First-Visit-Experience
| Feld | Typ | Default | Zweck |
| ----------------- | ------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| initialFeatures | Partial<Record<FeatureId, boolean>> | {} | Features, die bei erstem Besuch ohne persistierten State an sind. Wird vom Widget einmalig in den localStorage geseed — danach gewinnt das Persistierte |
Feature-Gating
| Feld | Typ | Default | Zweck |
| ------------------ | ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| disabledFeatures | FeatureId[] | [] | Features, die im Panel nicht angezeigt werden und auch via Profil-Preset nicht aktivierbar sind. Sinnvoll z. B. ['tts'] auf Seiten ohne Text-Content |
Legal / Compliance
| Feld | Typ | Default | Zweck |
| -------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| statementUrl | string | — | URL zur Barrierefreiheitserklärung (Panel-Footer-Link). Empfohlen für BFSG § 14 / EN 301 549 § 12.1.1. Nur http(s) + relative URLs; javascript: / data: werden blockiert |
Verhalten
| Feld | Typ | Default | Zweck |
| ---------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| draggableFab | boolean | false | End-User kann FAB per Maus/Touch/Shift+Arrow verschieben. Position persistiert in storageKey und wird beim nächsten Besuch angewendet. Reset im Panel löscht sie. |
| respectReducedMotion | boolean | true | Respektiere prefers-reduced-motion bei Animations-Features |
| hideOnPrint | boolean | true | FAB in Print-Medien ausblenden |
| debug | boolean | false | Silent-Failures (localStorage-Quota, Load-Fehler, Validation) als console.warn ausgeben |
Beispiel: Enterprise-Deployment
<script>
window.AccessibilityWidgetConfig = {
// Self-hosted Assets mit SRI-Pinning
corePath: '/assets/accessibility-widget-core.min.js',
cssPath: '/assets/accessibility-widget.min.css',
coreIntegrity: 'sha384-BEDPwzuDcF3GgBUbNzaOTpqKHhONBEr73ZCZLY0Kg3o8opuY7JDEdj/25LiIbM3A',
cssIntegrity: 'sha384-0t+ii2SnsYoSD/YuByxjc1EjkQOThsb5Z6ZqARi0WLkki39il/sfHcI6OzfqWrW9',
// Brand
locale: 'de',
primaryColor: '#0058a3',
position: 'bottom-left',
offset: { x: 24, y: 96 }, // Platz für Live-Chat drüber
zIndex: 9999, // unter In-Page-Dialogen
// Isolierter Storage-Namespace auf Multi-Tenant-Platform
storageKey: 'acme-prod-a11y',
// Onboarding: Fokus-Ring für Erstbesucher
initialFeatures: { focusOutline: true },
// TTS auf der Corporate-Site nicht anbieten (zu wenig Text-Content)
disabledFeatures: ['tts'],
// Compliance
statementUrl: '/barrierefreiheit',
debug: false,
};
</script>
<script src="/assets/accessibility-widget-loader.min.js" defer></script>Features
Das Widget stellt User-Preference-Features bereit — es verändert niemals ARIA/Semantik der Host-Seite. Alles läuft über data-aw-*-Attribute auf <html> und CSS-Filter.
| Feature | Wirkung |
| ------------------------------------------- | ------------------------------------------ |
| fontSize | 1 → 1.2 → 1.4 → 1.6 Textskalierung |
| lineHeight | 1.5 → 1.8 → 2.0 |
| letterSpacing | 0 → 0.05 → 0.1em |
| contrast | off / hoch / dunkel / invertiert |
| grayscale, invertColors, dyslexiaFont | Filter / Font-Overrides |
| highlightLinks | Unterstreichen, Rahmen, gelber Hintergrund |
| pauseAnimations | animation: none !important |
| bigCursor, focusOutline | Sichtbarkeitshilfen |
| readingMask, readingGuide | Lesehilfen |
| tts | Web Speech API, lokale TTS pro Locale |
| structureNav | Generierte Überschriften-Navigation |
Profile
Voreinstellungen aktivieren mehrere Features gleichzeitig: visionImpaired, motor, cognitive, seizureSafe, adhd, blind.
(Deaktivierte Features via disabledFeatures werden von Profilen nicht aktiviert.)
Runtime-API
| Method | Signatur | Zweck |
| ------------------- | --------------------- | ---------------------------------------------------------------- |
| open(opts?) | Promise<void> | Panel öffnen (lädt Core bei Bedarf) |
| close() | void | Panel schließen |
| reset() | void | Alle Präferenzen löschen + Page-Reload |
| set(id, value) | Promise<void> | Einzelnes Feature toggeln |
| applyProfile(id) | Promise<boolean> | Profil-Preset anwenden (6 Profile, siehe oben) |
| setLocale(locale) | Promise<boolean> | Sprache zur Laufzeit wechseln (persistent, Panel rerendert live) |
| setPosition(pos) | void | FAB an { x, y } setzen oder mit null auf Config-Anker zurück |
| getState() | WidgetState \| null | Persistenten State synchron lesen |
| on(event, cb) | () => void | Event abonnieren, gibt Unsubscribe zurück |
Beispiele
// Feature toggeln
window.AccessibilityWidget.set('fontSize', true);
// Profil anwenden
await window.AccessibilityWidget.applyProfile('visionImpaired');
// Sprache wechseln (ohne Reload, Panel rerendert)
await window.AccessibilityWidget.setLocale('fr');
// FAB programmatisch positionieren
window.AccessibilityWidget.setPosition({ x: 40, y: 200 });
window.AccessibilityWidget.setPosition(null); // zurück zum Config-Anker
// Events abonnieren (z. B. Analytics, privacy-friendly)
const off = window.AccessibilityWidget.on('stateChange', ({ state }) => {
console.log(
'active:',
Object.keys(state.features).filter((k) => state.features[k]),
);
});
off(); // später unsubscriben
// Alternativ nativ, ohne Helper
document.addEventListener('accessibility-widget:profileApplied', (e) => {
analytics.track('a11y_profile', e.detail.profile);
});Events
| Event | Payload | Feuert bei |
| ---------------- | -------------------------------------------- | ----------------------------------------------------- |
| stateChange | { state: WidgetState } | jeder State-Änderung (Feature, Profil, Locale, Reset) |
| open | { trigger: HTMLElement \| null } | Panel wird geöffnet |
| close | {} | Panel wird geschlossen |
| profileApplied | { profile: ProfileId; state: WidgetState } | Profil-Preset wird angewendet |
| localeChanged | { locale: Locale } | Locale wechselt |
| reset | {} | alle Präferenzen gelöscht |
Architektur
loader.ts— IIFE, ~5,7 KB gzip, injiziert Critical-CSS, rendert FAB, lädt Core bei Klickcore.ts— Panel-UI, Features, Focus-Trap, TTS; erst on-demand geladenconfig.ts— Config-Resolution mit Runtime-Validation (resolveConfig)state.ts— localStorage-Persistenz mit debug-aware catchesfeatures/*— 1 Datei pro Feature, tree-shakeablepanel/panel.ts— reine DOM-Komposition, keine Framework-Dep
Entwicklung
pnpm install
pnpm --filter @bauer-group/accessibility-widget build # baut dist/
pnpm --filter @bauer-group/accessibility-widget size # prüft Bundle-Budget
pnpm --filter @bauer-group/accessibility-widget test # VitestLizenz
@bauer-group/accessibility-widget ist dual lizenziert:
- GNU AGPL-3.0-only für Open-Source-Nutzung — siehe LICENSE.
- Kommerzielle Lizenz für geschlossene/proprietäre Produkte oder ohne AGPL-Offenlegungspflichten —
[email protected].
Details: LICENSING.md.
