Skip to content

Latest commit

 

History

History
261 lines (194 loc) · 8.04 KB

File metadata and controls

261 lines (194 loc) · 8.04 KB

Accessibility (a11y)

Accessibility utilities for ARIA attribute management, screen reader announcements, reduced motion detection, and skip navigation links.

Quick Start

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' });

AriaUtils

Safe ARIA attribute management with validation against the WAI-ARIA specification.

Methods

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').

Examples

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!

LiveAnnouncer

Screen reader live region announcements using ARIA live regions. Creates visually hidden <div> elements with aria-live attributes.

Factory Method

const announcer = LiveAnnouncer.create();

Instance Methods

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

Politeness Levels

Value Behavior
'polite' Queued after current speech (default)
'assertive' Interrupts current speech for urgent messages

Examples

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();

ReducedMotion

Detects and monitors the user's prefers-reduced-motion media query. Allows applications to respect accessibility motion preferences.

Methods

Method Returns Description
isReduced() boolean Check if user prefers reduced motion
onChange(handler) CleanupFn Listen for preference changes

Examples

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();

SSR Safety

Both isReduced() and onChange() safely handle server-side rendering environments where window or window.matchMedia is unavailable:

  • isReduced() returns false
  • onChange() returns a no-op cleanup function

SkipLink

Creates accessible "skip to content" links that become visible on keyboard focus. Essential for keyboard-only users to bypass repetitive navigation.

Factory Method

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
});

Configuration

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

Examples

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();

Built-in Styles

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

Behavior

  • 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 -1 to make it programmatically focusable
  • The link is inserted as the first child of document.body

Tree-Shakeable Import

// 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';

Accessibility Best Practices

  1. Use AriaUtils for all ARIA manipulation - The attribute validation prevents typos and invalid attribute names
  2. Prefer polite announcements - Only use 'assertive' for urgent messages (errors, time-sensitive alerts)
  3. Respect reduced motion - Check ReducedMotion.isReduced() before adding animations or transitions
  4. Add skip links to every page - Keyboard-only users need a way to bypass navigation
  5. Clean up resources - Call destroy() on LiveAnnouncer and the cleanup function from SkipLink when components unmount