# Interaction Policies

> Interaction Policies: anatomy, keyboard behavior, state, and theming in Askr.

Source: [https://askrjs.com/docs/components/interaction-policies](https://askrjs.com/docs/components/interaction-policies)

Status: stable. Packages: @askrjs/ui, @askrjs/themes.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

```tsx
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` (`'disabled'`) kicks in for a real `<button>`, while `DISABLED_ATTRIBUTES.asChild` (`'aria-disabled'`) applies when `asChild` is set and the rendered element isn't natively disableable — the constant itself only names that one attribute; the accompanying `tabindex="-1"` on a disabled `asChild` element is separate behavior the component applies, not a second value bundled into `DISABLED_ATTRIBUTES.asChild`.

## 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. Even the simplest components export one: `INPUT_A11Y_CONTRACT`, `TEXTAREA_A11Y_CONTRACT`, and `LABEL_A11Y_CONTRACT` all exist, alongside every other interactive component in the package — there's no tier of components that skips this.

## 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.

## Documentation navigation

[Previous](https://askrjs.com/docs/components/collections/index.md) | [Next](https://askrjs.com/docs/components/focus-and-dismissal/index.md)
