# Accessibility & i18n
All interactive Rokkit components support full keyboard
navigation and ARIA out of the box. This is implemented via two
primitives — **controllers** (state machines) and the
`navigator` action (DOM binding) — that you can also use to
build your own accessible components.
## What you get for free
- Arrow-key navigation in all lists, trees, menus, and selects.
- Home / End to jump to the first / last item.
- Enter / Space to select.
- Escape to close dropdowns and restore focus to the trigger.
- ARIA roles, states, and properties on every interactive element.
- Focus management — focus returns to the trigger on close.
- Type-ahead search where it makes sense (selects, dropdowns).
## The navigator pattern
Rokkit's interactivity is built on two composable primitives:
- **Controllers** — pure state machines (`ListController`,
`NestedController`, etc.) with no DOM dependency.
- **`navigator` action** — a Svelte action that binds a
controller to a container element, attaches keyboard
handlers, and manages ARIA attributes.
The separation means you can use a controller in tests **without
a browser**, and swap the visual rendering without touching the
interaction logic.
```svelte
{#each controller.flatView as node (node.key)}
-
{node.proxy.label}
{/each}
```
That's the entire interaction layer for an arrow-key-navigable
list. Same controller powers `List`, `Menu`, and
`MultiSelect` internally.
## Screen-reader semantics
Every component carries appropriate ARIA roles:
- `role="listbox"` / `role="menu"` on the parent container
- `role="option"` / `role="menuitem"` on each row
- `role="tree"` + `role="treeitem"` for hierarchies
- `role="status"` for live regions (StatusList, alerts)
- `role="tooltip"` + `aria-describedby` on Tooltip wrappers
ARIA labels come from the active locale's `messages` bundle, so
swapping locale changes the screen-reader announcements too.
## i18n
Localised labels live in `@rokkit/states`'s `messages` export.
Each component imports its own slice (`messages.tree`,
`messages.menu`, …) for default `aria-label`, "no results"
copy, etc. Override per-component via a `labels` prop or
globally via `setMessages()`.
## Focus rings
Components style `:focus-visible` (not `:focus`) so the ring
only shows on keyboard interaction, not pointer clicks. The
ring style itself is theme-driven — usually
`box-shadow: 0 0 0 2px var(--accent-soft)` — so it matches
whatever skin is active without per-component overrides.
## Reduced motion
Animation-bearing components (Carousel, Reveal, FloatingAction
expand, alert dismiss transitions) respect
`@media (prefers-reduced-motion)` automatically — animations
collapse to instantaneous transitions when the user has the OS
setting enabled.