Accessibility
Accessibility in Relements starts from the platform. Because the system is HTML-first, most accessibility comes for free: keyboard interaction, focus management, and the accessibility tree are the browser’s, not a re-implementation we have to keep correct. JavaScript only enhances; it is never required for a component to be operable.
This page is a 1.0 statement of intent and an honest map of what is and is not covered. It is not a formal WCAG audit or a VPAT.
Native semantics first
Section titled “Native semantics first”Components are built on the closest native element — <button>, <a href>, <dialog>, <details>/<summary>, <input>, <ol>, <nav> — so keyboard operability, focus order, and assistive-technology exposure are inherited from the browser rather than rebuilt in script. The reset is deliberately minimal and keeps native semantics intact; CSS styles those native elements, and the optional enhance* behaviors layer on richer interaction (roving tabindex, type-to-filter, anchored positioning) without taking over the base. A component that works with plain HTML works with a screen reader and a keyboard before a single line of JavaScript runs.
Visible focus
Section titled “Visible focus”base.css gives every interactive element a :focus-visible ring driven by the --re-shadow-focus token — a two-part box-shadow (an offset gap in the background color plus the ring color) so the indicator reads against any surface:
:focus-visible { outline: none; box-shadow: var(--re-shadow-focus); border-radius: var(--re-radius-sm);}Because box-shadow can’t be clipped to a single edge, components that live in tight or scrolled containers — the menu (and the context menu and command palette that reuse it), the tree, and the steps marker — opt out of the outer ring and use an inset ring instead (box-shadow: inset 0 0 0 2px var(--re-color-focus-ring)), which never gets cut off by overflow. Outer-ring components like banner keep the standard ring because their padding gives the ring room to breathe.
You retheme the ring by redeclaring the focus tokens — --re-color-focus-ring, --re-focus-ring-width, --re-focus-ring-offset, or the whole --re-shadow-focus (see Theming & tokens).
Forced colors (Windows High Contrast)
Section titled “Forced colors (Windows High Contrast)”Windows High Contrast Mode / forced-colors strips box-shadow and flattens author backgrounds, which would silently erase every focus ring and every “this one is selected” cue. Relements restores both.
A single rule in base.css re-establishes a real focus indicator for all focusable elements as an outline in the Highlight system color. It uses !important so it beats the components that opted into outline: none for their inset rings:
@media (forced-colors: active) { :focus-visible { outline: 2px solid Highlight !important; outline-offset: 1px; }}On top of that, components that signal state purely with a background or fill re-assert that state with system colors (Highlight / HighlightText, and Canvas / CanvasText for the stepper) inside their own @media (forced-colors: active) blocks. Covered today:
- Pagination — the
aria-current="page"item. - Tabs — the selected tab’s underline indicator.
- Toolbar —
aria-pressedtoggles (including while hovered, so the cue doesn’t flicker away under the pointer). - Combobox and command palette — the active/selected listbox row.
- Switch, checkbox, and radio (form controls) — the checked fill (and the indeterminate checkbox).
- Segmented — the checked option.
- Rating — filled stars, plus a real outline on the visible star (the radio is
sr-only, so the global ring would land on a 1px clipped box). - Steps — marker borders, the current step’s
Highlightring, and the connector segments. - Tree — the
aria-currentleaf.
Dark mode, contrast, and reduced motion
Section titled “Dark mode, contrast, and reduced motion”Dark mode is automatic. tokens.css remaps the role tokens under @media (prefers-color-scheme: dark), including the focus ring (it drops the offset gap that would otherwise read as two separate lines on a near-black background). See the Dark mode guide.
Contrast. Solid status fills (the data-emphasis="solid" banner, and the same approach in alerts/badges) use the *-700 color step paired with --re-color-text-on-accent, not -600 — -600 on white lands around 3.2–3.8:1 and fails WCAG AA for text, whereas every -700 shade clears AA (accent 6.6, danger 6.5, success 5.5, warning 5.0). Dismiss controls inherit that already-verified foreground rather than a translucent mix that would drop below 4.5:1.
Reduced motion. reset.css honors prefers-reduced-motion: reduce globally — animations and transitions are collapsed to ~0ms and scroll-behavior is forced to auto — so the whole system quiets down for users who ask for it:
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }}Honest ARIA stances
Section titled “Honest ARIA stances”We do not claim ARIA widget roles we can’t back with the full keyboard model. Adding a role without its expected interactions would mislead assistive technology, so a few components are deliberately less ARIA-decorated than they might first appear:
- Tree is navigation, not
role="tree". It’s a styled nested-disclosure tree built from native<nav>+ nested<details>/<summary>with<a href>/<button>leaves. Expand/collapse, focus order, and Enter/Space activation are all native and need zero JavaScript. It intentionally does not claimrole="tree", because the tree widget keyboard model (Up/Down between items, Right/Left to expand, etc.) is out of scope — claiming the role without the model would mislead AT. - Steps is a display indicator, not a tablist. It’s an ordered process indicator on a native
<ol>. There is no roving tabindex, norole="tab", and no panels. The markers are decorative (aria-hidden), so completion is exposed to AT with a visually-hidden status word per step plusaria-current="step"on the current<li>.
aria-current, role="list", and .re-sr-only
Section titled “aria-current, role="list", and .re-sr-only”aria-currentmarks the active item in navigation-style components:pageon the current breadcrumb crumb, pagination page, and<a href>tree leaf;stepon the current steps item;trueon a<button>tree leaf.role="list"on unordered nav lists. Tree putsrole="list"on every<ul>so Safari/VoiceOver keep announcing “list, N items” even thoughlist-style: nonewould otherwise drop list semantics. Ordered lists are treated differently: steps and breadcrumb strip the list visually in CSS and keep native<ol>ordered semantics rather than addingrole="list", which would downgrade the<ol>to a generic list and kill the “N of M” position read..re-sr-onlyis the shared visually-hidden utility (inbase.css) for text that should reach screen readers but not the screen — per-star rating labels, the tags-input live region, and the steps status words all use it.
Testing
Section titled “Testing”Every component ships an axe accessibility spec (tests/a11y/<name>.a11y.spec.ts), run on Chromium, Firefox, and WebKit on every pull request alongside behavior and visual-regression tests (see Browser support). A dedicated forced-colors suite (tests/a11y/forced-colors.spec.ts) emulates High Contrast Mode and asserts that the focus outline survives, that the inset-ring opt-outs still show an outline, and that selected/current/checked/pressed state is re-established with system colors. Forced-colors emulation is Chromium-only.
These checks catch a wide class of regressions, but they are automated tests — not a substitute for a formal WCAG conformance audit, manual screen-reader testing across the matrix, or a published VPAT. Treat this guide as a statement of design intent and verified coverage, not a certification.
Related
Section titled “Related”- HTML-first policy — why native semantics come first.
- Theming & tokens — the focus-ring and color tokens you can override.
- Dark mode — automatic
prefers-color-schemesupport. - Browser support — what we test and where.