Skip to Content
⭐️ Leave a star →
HooksuseControllableState

useControllableState

Unified state for components that can be controlled or uncontrolled. When value is provided, the parent owns the state and the setter only fires onChange. When value is omitted, the hook keeps state internally and fires onChange on each change.

This is the same primitive Wire UI uses internally on every controllable component (Modal, Drawer, Tabs, Accordion, …) — exported so your own components can offer the same dual API.

import { useControllableState } from '@wire-ui/react' interface SwitchProps { checked?: boolean defaultChecked?: boolean onCheckedChange?: (checked: boolean) => void } function Switch({ checked, defaultChecked, onCheckedChange }: SwitchProps) { const [isOn, setOn] = useControllableState({ value: checked, defaultValue: defaultChecked ?? false, onChange: onCheckedChange, }) return ( <button type="button" role="switch" aria-checked={isOn} onClick={() => setOn(!isOn)} > {isOn ? 'On' : 'Off'} </button> ) }

Behavior

  • When value is provided (defined), the hook is controlled: writes do not mutate internal state — they only call onChange. The parent must react by updating its own state.
  • When value is undefined, the hook is uncontrolled: writes update internal state and call onChange.
  • Switching between controlled and uncontrolled across a component’s lifetime is unsupported.

Options

OptionTypeDescription
valueT | undefinedWhen defined, makes the hook controlled.
defaultValueTInitial value in uncontrolled mode.
onChange(value: T) => voidCalled on every change in both modes.

Returns

A [value, setValue] tuple, similar to useState. setValue accepts either a new value or an updater function.
Last updated on

MIT License © 2026 wire-ui

useControllableState – Wire UI