useDisclosure

Open and closed state for an overlay.

The controlled-or-uncontrolled pattern Dialog, BottomSheet and Select implement internally, lifted out for when the overlay has to be driven from somewhere else — closing a sheet once a request settles, or opening one from another screen.

Usage

import { useDisclosure, BottomSheet, Button, Input } from 'panelui-native';

function ShareSheet() {
  const sheet = useDisclosure();

  return (
    <>
      <Button onPress={sheet.open}>Share</Button>

      <BottomSheet {...sheet.props}>
        <BottomSheet.Content>
          <Input placeholder="https://panelui.dev/p/xK2f9" />
          <Button onPress={sheet.close}>Done</Button>
        </BottomSheet.Content>
      </BottomSheet>
    </>
  );
}

props spreads straight onto any PanelUI overlay — it is { open, onOpenChange }. Requests equal to the accepted state are ignored, so calling open() twice does not emit a duplicate change.

Controlled requests remain owner-driven until the open prop accepts them; external prop resets do not call onOpenChange. If ownership changes from controlled to uncontrolled, the hook retains the last accepted controlled value rather than restoring its old default.

Examples

Closing once a request settles

The reason to lift the state out at all — the overlay cannot close itself from inside an async handler.

const dialog = useDisclosure();
const [saving, setSaving] = useState(false);

async function save() {
  setSaving(true);
  try {
    await api.save(draft);
    dialog.close();
    toast({ variant: 'success', title: 'Saved' });
  } finally {
    setSaving(false);
  }
}

<Dialog {...dialog.props}>
  <Dialog.Content>
    <Dialog.Title>Edit name</Dialog.Title>
    <Input defaultValue={project.name} />
    <Dialog.Footer>
      <Button variant="ghost" onPress={dialog.close}>Cancel</Button>
      <Button loading={saving} onPress={save}>Save</Button>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog>;

Controlled

Pass open to drive it from state you already own — a router param, a wizard step, a query result.

const sheet = useDisclosure({
  open: step === 'share',
  onOpenChange: handleChange,
});

Reacting to the state elsewhere

const menu = useDisclosure();

<>
  <Button onPress={menu.toggle}>
    {menu.isOpen ? 'Hide filters' : 'Show filters'}
  </Button>

  <BottomSheet {...menu.props}>
    <BottomSheet.Content>{/* … */}</BottomSheet.Content>
  </BottomSheet>
</>;

Several overlays on one screen

Each gets its own instance — there is no shared registry to collide over.

const share = useDisclosure();
const confirmDelete = useDisclosure();

<BottomSheet {...share.props}>{/* … */}</BottomSheet>
<Dialog {...confirmDelete.props}>{/* … */}</Dialog>

API Reference

Options

OptionTypeDefaultDescription
defaultOpenbooleanfalseInitial state when uncontrolled.
openbooleanControlled state. Providing it makes the hook controlled.
onOpenChange(open: boolean) => voidCalled on every change, controlled or not.

Returns

ValueTypeDescription
isOpenbooleanCurrent state.
open() => voidOpens.
close() => voidCloses.
toggle() => voidFlips.
props{ open, onOpenChange }Spread onto an overlay component.

On this page