Accessibility
Use accessibility with the current published Askr packages.
@askrjs/askr0.0.59
How to use accessibility
Start with semantic elements and themed components, supply application labels and descriptions, then verify the complete task with keyboard and screen reader.
- Import the published @askrjs/askr entrypoint.
- Keep accessibility configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { Button, Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from '@askrjs/themes/components';
<Dialog>
<DialogTrigger asChild><Button>Delete project</Button></DialogTrigger>
<DialogContent>
<DialogTitle>Delete project?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
<DeleteProjectActions />
</DialogContent>
</Dialog>Goal and architecture
`@askrjs/ui` bakes accessibility into each component as a documented, typed contract rather than leaving it to the consumer — components like `Button`, `Toast`, `Dropdown`, and `FocusScope` each export an `_A11Y_CONTRACT` constant (e.g. `BUTTON_A11Y_CONTRACT`) describing their ARIA role, keyboard activation keys, disabled-state attributes, and focus rules, following the WAI-ARIA APG patterns. Interaction behavior itself is centralized in shared foundations (the `pressable` foundation drives keyboard activation for `Button`, for instance) so every interactive component gets the same keyboard and focus handling instead of each component reimplementing it slightly differently.
Implementation
Use the components as designed rather than reaching for `asChild` unless you need to render a different host element — `asChild` mode preserves the child's role but switches disabled handling from the native `disabled` attribute to `aria-disabled` plus `tabindex="-1"`, since a non-native element has no built-in disabled state. Compound components like `Dialog`, `Popover`, and `Menu` build on `FocusScope` to trap and restore focus around overlays; let them own that behavior rather than manually managing `tabindex` and `document.activeElement` yourself. For custom interactive elements outside the component set, check the relevant `*_A11Y_CONTRACT` for the closest existing component as a reference for what role, keyboard support, and ARIA attributes are expected.
Failure states
A component that does not respond to its documented keyboard shortcuts (Space/Enter for `Button`, arrow keys for `Menu`/`RadioGroup`) is a contract violation, not a style choice — check the component's `.a11y.d.ts` file for the exact keys it commits to before assuming custom behavior is fine. Disabled elements must be genuinely out of the tab order (native `disabled` or `tabindex="-1"` per the contract's `FOCUS_RULES`), so a visually-disabled button a screen reader user can still tab into and activate is a real bug. Overlay components that don't restore focus to their trigger on close break keyboard navigation for the next action — treat that as equivalent to a broken click handler.
Verification
Assert against the exported `*_A11Y_CONTRACT` constants directly in tests — e.g. confirm a `Button`'s rendered `role` and keyboard handling match `BUTTON_A11Y_CONTRACT.ROLE` and `.KEYBOARD_ACTIVATION` — so a regression in the component's markup fails a test instead of only showing up in manual screen-reader testing. Run keyboard-only and screen-reader passes on any custom composition of overlay primitives (`Dialog` + `FocusScope`, `Popover` + `DismissableLayer`), since contracts describe the primitives correctly but your composition can still break focus order or announcement timing.