Skip to content
Redpanda UI
Esc
↑↓navigate↵open⌘Jpreview
On this page

useControllableState

Support both controlled and uncontrolled usage of a stateful value from a single hook.

Installation

npx shadcn@latest add @redpanda/use-controllable-state
pnpm dlx shadcn@latest add @redpanda/use-controllable-state
yarn dlx shadcn@latest add @redpanda/use-controllable-state
bunx shadcn@latest add @redpanda/use-controllable-state
nubx shadcn@latest add @redpanda/use-controllable-state
aube dlx shadcn@latest add @redpanda/use-controllable-state

useControllableState lets a component work in either controlled or uncontrolled mode from one hook. When a prop is provided the value is controlled and changes are reported through onChange; when it’s omitted the hook holds its own state seeded from defaultProp. This is the pattern behind value/defaultValue pairs across the registry’s form and disclosure components.

In development it warns if a component flips between controlled and uncontrolled between renders.

Usage

import { useControllableState } from "@/hooks/use-controllable-state";

function Toggle({
  pressed,
  defaultPressed = false,
  onPressedChange,
}: {
  pressed?: boolean;
  defaultPressed?: boolean;
  onPressedChange?: (pressed: boolean) => void;
}) {
  const [value, setValue] = useControllableState({
    prop: pressed,
    defaultProp: defaultPressed,
    onChange: onPressedChange,
    caller: "Toggle",
  });

  return (
    <button type="button" aria-pressed={value} onClick={() => setValue((p) => !p)}>
      {value ? "On" : "Off"}
    </button>
  );
}

API

useControllableState<T>({ prop, defaultProp, onChange?, caller? })

Option Type Description
prop T | undefined The controlled value. When defined, the hook is controlled; when undefined, uncontrolled.
defaultProp T Initial value used in uncontrolled mode.
onChange (state: T) => void Called whenever the value changes (in both modes).
caller string Component name used in the dev-only controlled/uncontrolled warning.

Returns [value, setValue] — a useState-compatible tuple. setValue accepts a next value or an updater function.

Credits

Adapted from Radix UI’s useControllableState.

Last updated on October 7, 2026

Was this page helpful?