Accessibility utilities for ARIA attribute management, screen reader announcements, reduced motion detection, and skip navigation links.
import {
AriaUtils,
LiveAnnouncer,
ReducedMotion,
SkipLink,
} from '@zappzarapp/browser-utils/a11y';
// Set ARIA attributes safely
AriaUtils.set(button, 'expanded', 'true');
AriaUtils.setRole(dialog, 'dialog');
// Screen reader announcements
const announcer = LiveAnnouncer.create();
announcer.announce('Item added to cart');
// Respect reduced motion preferences
if (ReducedMotion.isReduced()) {
// Use simple transitions or no animation
}
// Skip navigation link
const cleanup = SkipLink.create({ targetId: 'main-content' });Safe ARIA attribute management with validation against the WAI-ARIA specification.
| Method | Returns | Description |
|---|---|---|
set(element, attribute, value) |
void |
Set an ARIA attribute |
get(element, attribute) |
string | null |
Get an ARIA attribute value |
remove(element, attribute) |
void |
Remove an ARIA attribute |
toggle(element, attribute) |
string |
Toggle a boolean ARIA attribute |
setRole(element, role) |
void |
Set the role attribute |
getRole(element) |
string | null |
Get the role attribute |
removeRole(element) |
void |
Remove the role attribute |
validateAttribute(attribute) |
void |
Validate an ARIA attribute name |
All attribute methods validate the attribute name against the known WAI-ARIA
attribute set and throw ValidationError for invalid names. The attribute
parameter is the name without the aria- prefix (e.g., 'expanded', not
'aria-expanded').
import { AriaUtils } from '@zappzarapp/browser-utils/a11y';
// Toggle a disclosure button
const button = document.querySelector('.disclosure-button')!;
AriaUtils.set(button, 'expanded', 'false');
AriaUtils.set(button, 'controls', 'content-panel');
button.addEventListener('click', () => {
AriaUtils.toggle(button, 'expanded');
});
// Set roles
AriaUtils.setRole(element, 'dialog');
AriaUtils.setRole(element, 'alert');
// Read and remove
const expanded = AriaUtils.get(button, 'expanded'); // 'true' | 'false' | null
AriaUtils.remove(button, 'expanded');
// Invalid attribute throws ValidationError
AriaUtils.set(button, 'notreal', 'value'); // throws!Screen reader live region announcements using ARIA live regions. Creates
visually hidden <div> elements with aria-live attributes.
const announcer = LiveAnnouncer.create();| Method | Returns | Description |
|---|---|---|
announce(message, politeness) |
void |
Announce a message to screen readers |
clear() |
void |
Clear the current announcement |
destroy() |
void |
Remove DOM elements and clean up |
| Value | Behavior |
|---|---|
'polite' |
Queued after current speech (default) |
'assertive' |
Interrupts current speech for urgent messages |
import { LiveAnnouncer } from '@zappzarapp/browser-utils/a11y';
const announcer = LiveAnnouncer.create();
// Polite announcement (default) - waits for current speech to finish
announcer.announce('3 items loaded');
announcer.announce('Settings saved successfully');
// Assertive announcement - interrupts current speech
announcer.announce('Error: form validation failed', 'assertive');
announcer.announce('Connection lost', 'assertive');
// Clear announcement
announcer.clear();
// Cleanup when component unmounts
announcer.destroy();Detects and monitors the user's prefers-reduced-motion media query. Allows
applications to respect accessibility motion preferences.
| Method | Returns | Description |
|---|---|---|
isReduced() |
boolean |
Check if user prefers reduced motion |
onChange(handler) |
CleanupFn |
Listen for preference changes |
import { ReducedMotion } from '@zappzarapp/browser-utils/a11y';
// Check current preference
if (ReducedMotion.isReduced()) {
element.style.transition = 'none';
} else {
element.style.transition = 'transform 0.3s ease';
}
// React to changes
const cleanup = ReducedMotion.onChange((reduced) => {
if (reduced) {
disableAnimations();
} else {
enableAnimations();
}
});
// Cleanup when done
cleanup();Both isReduced() and onChange() safely handle server-side rendering
environments where window or window.matchMedia is unavailable:
isReduced()returnsfalseonChange()returns a no-op cleanup function
Creates accessible "skip to content" links that become visible on keyboard focus. Essential for keyboard-only users to bypass repetitive navigation.
const cleanup = SkipLink.create({
targetId: 'main-content',
text: 'Skip to main content', // optional, this is the default
className: 'custom-skip-link', // optional, uses built-in styles by default
});| Option | Type | Default | Description |
|---|---|---|---|
targetId |
string |
(required) | ID of the target element |
text |
string |
'Skip to main content' |
Text displayed in the skip link |
className |
string |
undefined |
CSS class for custom styling |
import { SkipLink } from '@zappzarapp/browser-utils/a11y';
// Basic skip link (uses built-in styling)
const cleanup = SkipLink.create({
targetId: 'main-content',
});
// Custom text
const cleanup2 = SkipLink.create({
targetId: 'search-results',
text: 'Skip to search results',
});
// Custom styling via CSS class
const cleanup3 = SkipLink.create({
targetId: 'main-content',
className: 'my-skip-link',
});
// Cleanup when SPA route changes
cleanup();Without a className, the skip link is:
- Visually hidden by default (positioned off-screen)
- Visible on focus with a fixed position overlay (black background, white text, 14px font, 4px border radius)
- Hidden again on blur
- Clicking the link prevents default navigation and focuses the target element
- If the target element doesn't have a
tabindex, one is automatically set to-1to make it programmatically focusable - The link is inserted as the first child of
document.body
// Import only what you need
import { AriaUtils } from '@zappzarapp/browser-utils/a11y';
import { LiveAnnouncer } from '@zappzarapp/browser-utils/a11y';
import { ReducedMotion } from '@zappzarapp/browser-utils/a11y';
import { SkipLink } from '@zappzarapp/browser-utils/a11y';- Use AriaUtils for all ARIA manipulation - The attribute validation prevents typos and invalid attribute names
- Prefer polite announcements - Only use
'assertive'for urgent messages (errors, time-sensitive alerts) - Respect reduced motion - Check
ReducedMotion.isReduced()before adding animations or transitions - Add skip links to every page - Keyboard-only users need a way to bypass navigation
- Clean up resources - Call
destroy()on LiveAnnouncer and the cleanup function from SkipLink when components unmount