# Controlled State

> Controlled State: anatomy, keyboard behavior, state, and theming in Askr.

Source: [https://askrjs.com/docs/components/controlled-state](https://askrjs.com/docs/components/controlled-state)

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 { For, state } from '@askrjs/askr';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@askrjs/themes/components';

function ProjectStatusSelect() {
  const [status, setStatus] = state('active');
  return <Select value={status()} onValueChange={setStatus}>
    <SelectTrigger aria-label="Project status"><SelectValue /></SelectTrigger>
    <SelectContent>
      <For each={statuses} by={(item) => item.id}>
        {(item) => <SelectItem value={item.id}>{item.label}</SelectItem>}
      </For>
    </SelectContent>
  </Select>;
}
```

## Purpose

Nearly every stateful component in @askrjs/ui — Checkbox, Switch, RadioGroup, Select, Accordion, ToggleGroup, Dialog, Popover, Dropdown, Menubar — follows the same value/change-handler naming convention, so once you've controlled one you've effectively controlled all of them. This page documents that shared convention rather than repeating it on every component page.

## Install and import

There's no separate import for this — it's a prop-naming pattern baked into each component's own types, e.g. `CheckboxOwnProps`, `AccordionSingleProps`, `SelectOwnProps`. You use it just by passing the relevant props when you import the component itself.

## Live examples

`<Checkbox checked={checked} onCheckedChange={setChecked} />` is fully controlled — the component renders exactly what `checked` says and calls back on every toggle. Drop `checked`/`onCheckedChange` and pass `defaultChecked` instead, and the same component manages its own internal state, useful for a form where you don't need to observe every change.

## Anatomy

The pattern has three parts per value it tracks: a controlled prop (`value`, `checked`, `open`), an uncontrolled seed (`defaultValue`, `defaultChecked`, `defaultOpen`), and a change callback (`onValueChange`, `onCheckedChange`, `onOpenChange`). Select is a good example of a component tracking two independent pieces of state this way at once — `value`/`defaultValue`/`onValueChange` for the selected option and `open`/`defaultOpen`/`onOpenChange` for the popover's visibility.

## State model

Passing the controlled prop (even `undefined` isn't the trigger — actually passing the named prop key is) puts the component in controlled mode for that value; omitting it and optionally passing the `default*` variant puts it in uncontrolled mode. Checkbox's docs note explicitly that `indeterminate` is visual-only and independent of `checked` — setting it doesn't imply or require a particular checked value, so you manage it as its own boolean regardless of controlled/uncontrolled status.

## Keyboard and accessibility

Controlled or uncontrolled makes no difference to keyboard behavior — Space still toggles Checkbox and Switch, arrow keys still move focus in RadioGroup and ToggleGroup — because that logic lives in the component's internal interaction handling, not in whichever half of the value it happens to own at a given moment. The distinction only matters for how the resulting value change is surfaced to your code.

## Styling and tokens

Whichever mode you're in, the rendered `data-state` (e.g. `data-state="checked"` or `data-state="open"`) reflects the resolved current value, so themed CSS never needs to know or care whether that value came from your state or the component's internal state.

## API

Representative shapes across the library: Checkbox/Switch use `checked?: boolean`, `defaultChecked?: boolean`, `onCheckedChange?: (checked: boolean) => void`; RadioGroup/ToggleGroup/Select use `value?: string`, `defaultValue?: string`, `onValueChange?: (value: string) => void` (or `string[]` for multi-select ToggleGroup and multi-mode Accordion); overlay triggers use `open?: boolean`, `defaultOpen?: boolean`, `onOpenChange?: (open: boolean) => void`.

## Edge cases

Switching a component between controlled and uncontrolled across renders (e.g. `checked={isEditing ? checked : undefined}`) isn't a pattern any of these components are designed to support cleanly — pick one mode when the component mounts and stick with it, matching the same rule React itself applies to controlled inputs. If you pass both `value` and `defaultValue`, the controlled `value` wins and `defaultValue` is simply ignored, since it only seeds initial state in uncontrolled mode.

## Related pages

See Collections for how this convention extends to multi-item selection state. See the individual component pages (Checkbox, Switch, Select, Accordion) for the exact prop names each one uses.

## Documentation navigation

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