Interaction Policies
Use interaction policies with the current published Askr packages.
@askrjs/ui0.0.13@askrjs/themes0.0.13
How to use interaction policies
Interaction Policies should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.
- Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
- Keep interaction policies labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
- Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Button, Dialog, DialogContent, DialogTitle, DialogTrigger } from '@askrjs/themes/components';
<Dialog>
<DialogTrigger asChild><Button>Open settings</Button></DialogTrigger>
<DialogContent>
<DialogTitle>Project settings</DialogTitle>
<ProjectSettings />
</DialogContent>
</Dialog>Purpose
Every interactive @askrjs/ui component ships a companion `*_A11Y_CONTRACT` object — `BUTTON_A11Y_CONTRACT`, `ACCORDION_A11Y_CONTRACT`, `SWITCH_A11Y_CONTRACT`, and so on — that documents its ARIA role, keyboard bindings, and data attributes as a typed, importable value rather than as prose you have to trust. This page explains what these contracts are for and how to read them, since they're the closest thing @askrjs/ui has to a formal interaction spec per component.
Install and import
Contracts are exported alongside their component from the same entrypoint, e.g. `import { Button, BUTTON_A11Y_CONTRACT } from '@askrjs/ui'` or from the per-component subpath `import { BUTTON_A11Y_CONTRACT } from '@askrjs/ui/button'`. You don't need them at runtime to use a component correctly — they're most useful for testing and for verifying your own asChild wrapper preserves the expected behavior.
Live examples
`BUTTON_A11Y_CONTRACT.KEYBOARD_ACTIVATION` is `['Enter', 'Space']` and `BUTTON_A11Y_CONTRACT.DISABLED_ATTRIBUTES` distinguishes the native `disabled` attribute from the `aria-disabled` used when `asChild` renders something other than a real `<button>`. `DISMISSABLE_LAYER_A11Y_CONTRACT.DISMISS_EVENTS` lists `['escape-key', 'outside-pointer']`, which is exactly the set of interactions DismissableLayer listens for by default.
Anatomy
Each contract is a `declare const ..._A11Y_CONTRACT` frozen object with a matching type alias (`ButtonA11yContract`, `SwitchA11yContract`, etc.) derived via `typeof`, so you get autocomplete and type-checking against the literal values rather than a loosely typed record. Fields are grouped by concern — role, keyboard activation, disabled-state attributes, focus rules, and the `DATA_ATTRIBUTES` map of stable `data-*` hooks — and not every component populates every group, since not every component needs one.
State model
The contracts themselves are static — they don't change at runtime and carry no component state. What varies at runtime is which branch of a contract applies: Button's `DISABLED_ATTRIBUTES.native` kicks in for a real `<button>`, while `DISABLED_ATTRIBUTES.asChild` (`aria-disabled` plus `tabindex="-1"`) applies when `asChild` is set and the rendered element isn't natively disableable.
Keyboard and accessibility
This is the primary content of most contracts. `KEYBOARD_ACTIVATION` lists which keys trigger a press-like interaction (`Enter`/`Space` for Button and Switch), and `DISMISS_EVENTS` on DismissableLayer's contract lists which interactions close an overlay. Reading a component's contract before wiring custom keyboard handling around it tells you what's already handled internally versus what you still need to implement yourself.
Styling and tokens
`DATA_ATTRIBUTES` inside each contract is the styling-relevant part: it names the `data-slot`, `data-state`, and `data-disabled` (or similarly-purposed) attributes a component sets on its DOM output. `@askrjs/themes` selectors are written against exactly these attribute names, so if you build a custom theme, the contract tells you which hooks are guaranteed stable rather than incidental implementation detail.
API
There's no single generic contract type — each component defines its own, e.g. `ButtonA11yContract`, `AccordionA11yContract`, `SwitchA11yContract`, `DismissableLayerA11yContract`, `FocusScopeA11yContract`. Import the constant when you want the actual values (for tests or docs generation) and the type when you only need to constrain a function parameter to 'something shaped like a contract'.
Edge cases
Contracts describe intended behavior, not a runtime guarantee enforced by the library — nothing stops a consumer from overriding `role` or stripping a `data-*` attribute via passthrough props, so treat them as documentation-you-can-import rather than a validator. Some simpler components (Input, Textarea, Label) don't export a contract at all because they rely on native HTML semantics that don't need restating.
Related pages
See ARIA and Ref Utilities for how these contracts relate to the `asChild` pattern and ref forwarding. See Focus and Dismissal for the two components — FocusScope and DismissableLayer — whose entire job is implementing one interaction policy each.