Table stakes, not a feature. Bones treats accessibility as part of a component being correct, the same as its visual states.

Behavior comes from Base UI

Interactive components wrap Base UI parts, which handle the hard parts: roles and ARIA attributes, keyboard interaction, focus management, and screen reader semantics. Bones adds styling on top and deliberately avoids reimplementing behavior, so those guarantees stay intact.

Focus is always visible

Every focusable part shows a ring on keyboard focus, driven by the --ub-ring token so it stays visible in both themes and any custom theme. Rings are scoped per component, never a global rule; text fields ring on every focus so the caret's destination is never in doubt:

.ub-button:focus-visible {
  outline: 2px solid var(--ub-ring);
  outline-offset: 2px;
}

Reduced motion

All animation runs through duration tokens, and those collapse to zero when someone prefers reduced motion. There is no per-component opt-in to forget.

ARIA attributes always pass through

Every component spreads your props onto the underlying element last, so aria-label, aria-describedby, and friends always land where they should. Variants that can lack a visible label (like icon-only tabs) warn in development when no accessible name is provided, so the gap is caught before it ships.

Sensible defaults

  • Buttons default to type="button", so forms don't submit by accident.
  • Icons render aria-hidden by default; they're visual unless you say otherwise.
  • Body and secondary text meet WCAG AA contrast in both themes; tertiary and disabled text sit deliberately below it and never carry meaning alone.

Found a gap?

Accessibility issues are treated as bugs. Open an issue on GitHub with the component, your setup, and what you expected.