Skip to main content

Accessibility

ARIA Live Regions

Announce messages to screen readers:

js
import { announce, aria, role } from 'weblisk/a11y/aria.js';

// Polite announcement (waits for user to finish reading)
announce('3 new items loaded');

// Assertive announcement (interrupts)
announce('Error: Connection lost', 'assertive');

// Declarative ARIA attributes
aria('#dialog', { modal: true, labelledby: 'dialog-title' });

// Set a role with required attributes
role('#tab-panel', 'tabpanel', { labelledby: 'tab-1' });

Focus Management

Focus Trap

Trap focus within a container (modals, dialogs):

js
import { focusTrap } from 'weblisk/a11y/focus.js';

const trap = focusTrap('#modal');
trap.activate();   // Tab now cycles within #modal
trap.deactivate(); // Release focus

Roving Tabindex

Arrow key navigation within a group (tabs, toolbars, menus):

js
import { rovingIndex } from 'weblisk/a11y/focus.js';

const cleanup = rovingIndex('#toolbar', 'button', {
  horizontal: true,
  wrap: true,    // Arrow keys wrap around
});

Skip Link

Add a "Skip to main content" link for keyboard navigation:

js
import { skipLink } from 'weblisk/a11y/focus.js';

skipLink('main-content');
// Creates: <a class="wl-skip" href="#main-content">Skip to main content</a>

Reduced Motion

Respect the user's motion preference:

js
import { motionReduced, motion, safeTransition, ifMotion } from 'weblisk/a11y/motion.js';

// Reactive signal
effect(() => {
  if (motionReduced()) {
    console.log('User prefers reduced motion');
  }
});

// Choose value based on preference
const duration = motion('0.3s', '0s');

// Only set transition if motion is allowed
safeTransition('#panel', 'transform 0.3s ease');

// Run function only if motion is allowed
ifMotion(() => {
  el.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300 });
});

Global CSS Override

Inject CSS that disables all animations when reduced motion is preferred:

js
import { injectReducedMotionCSS } from 'weblisk/a11y/motion.js';

injectReducedMotionCSS();

This adds:

css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Locale & Internationalization

Signal-driven locale detection and Intl API formatting wrappers:

js
import { locale, setLocale, textDirection, formatter } from 'weblisk/a11y/locale.js';

// Reactive locale signal (reads html[lang] or navigator.language)
console.log(locale()); // 'en-US'

// Switch locale — all formatters update reactively
setLocale('fr');

// Text direction signal for RTL support
const dir = textDirection(); // 'ltr' or 'rtl'

// Intl formatters tied to the locale signal
const fmt = formatter();
fmt.number(1234.5);              // '1,234.5' or '1 234,5'
fmt.date(new Date());             // locale-formatted date
fmt.currency(99.99, 'USD');       // '$99.99' or '99,99 $US'
fmt.relative(-2, 'day');         // '2 days ago' or 'il y a 2 jours'
fmt.list(['A', 'B', 'C']);        // 'A, B, and C' or 'A, B et C'

No translation dictionaries — uses the browser's native Intl API for locale-aware formatting.