Skip to content

Accessibility

Hypermedia Components keeps state in HTML attributes (aria-*, data-*, native :checked / disabled), so assistive technology gets the semantics for free. Beyond that, the kit adapts to the user’s context in CSS: Forced Colors, reduced motion, and right-to-left layout.

When a user turns on a high-contrast / forced-colors theme, the browser replaces author colors with the user’s system palette and drops box-shadow. Two HC patterns would otherwise break:

  1. Focus rings drawn with box-shadow (the inputs, selects, custom toggles, slider) — the ring disappears.
  2. State conveyed only by a background tint (color-mix) — a selected option, the current pagination page, a checked control — becomes indistinguishable.

A single forced-colors stylesheet (hc.a11y.css, shipped inside the main hc.css bundle) re-expresses both using CSS system colors, which forced-colors mode preserves:

  • Focus is restored as a real outline (Highlight) wherever a component used outline: none plus a box-shadow ring.
  • Selection / active state (combobox & command options, tabs, toggle group, pagination, calendar days, datagrid selected rows / current cell) is marked with an inset outlineoutline survives forced-colors, so the mark is always visible.
  • Custom toggles (hc-checkbox, hc-radio, hc-switch, the hc-slider thumb) opt out with forced-color-adjust: none and paint with Highlight / HighlightText / Canvas / CanvasText, so the checked fill and thumb stay visible.

You get this automatically with the bundled hc.css. With the granular entry, import the stylesheet after your component CSS:

@import "@hypermedia-components/core/css/core";
@import "@hypermedia-components/core/css/button";
/* …other components… */
@import "@hypermedia-components/core/a11y.css"; /* last */

hc.a11y.css is concatenated into the hc.components layer as the last source file, so it overrides each component’s own focus / state rules by source order — no extra cascade layer, no specificity hacks.

Emulate forced-colors in Chromium DevTools (Rendering → Emulate CSS media feature forced-colors), or in Playwright:

await page.emulateMedia({ forcedColors: 'active' });

Focus an input, tab through a combobox, toggle a checkbox — the indicators stay visible in the system palette.

When the user sets prefers-reduced-motion, every animated component neutralises its transition duration so state changes are instant rather than animated. This is handled in CSS — no JavaScript, no configuration.

Components that animate in their own stylesheet (accordion, drawer, progress, skeleton, spinner, switch, the OTP caret, the shell overlay, datagrid) gate themselves; the remaining controls whose only motion is a transition (button, checkbox, radio, input, select, datepicker, tabs, pagination, toggle group) are covered centrally in hc.a11y.css, and the htmx indicator fade in hc.htmx.css. The net effect: nothing in the kit animates under reduced-motion.

// Verify in Playwright:
await page.emulateMedia({ reducedMotion: 'reduce' });
// getComputedStyle(button).transitionDuration === '0s'

Motion that conveys meaning (a spinner indicating progress) keeps its role via aria-*; reduced-motion only removes the decorative animation.

Set dir="rtl" on <html> (or any subtree) and the kit follows. Layout is built on CSS logical properties (margin-inline, inset-inline-start, text-align: start, border-inline-end…), so spacing, alignment, borders, and the components’ internal geometry mirror automatically — there are no physical left / right rules to flip.

A few spots need genuine direction-awareness, and are handled for you:

  • Datagrid frozen columns stick to the inline-start edge (right, in RTL), and the freeze-line shadow falls on the correct side.
  • Calendar previous / next chevrons mirror so “previous” still points the natural way.
  • Keyboard navigation. Horizontal / are mirrored in RTL for the tabs, toggle group, datagrid cell grid, calendar, and splitter — moves to the inline-end (next) item. Vertical / are unchanged.

You can flip the whole documentation site to RTL with the Dir picker in the top bar to preview every component.