Pho Design System

Drawer

A panel that slides in from the edge — here, the right — with swipe-to-dismiss. Built on Base UI Drawer. Use it for secondary tasks and navigation that should keep the page context behind it.

Import

import { Drawer } from "@photon-ai/pho-ui/components/drawer";

Basic

Compose Drawer.PortalBackdropViewportPopupContent. Wrap action buttons in Drawer.Close to dismiss.

<Drawer.Root>
  <Drawer.Trigger render={<Button variant="outlined">Open settings</Button>} />
  <Drawer.Portal>
    <Drawer.Backdrop />
    <Drawer.Viewport>
      <Drawer.Popup>
        <Drawer.Content>
          <Drawer.Title>Project settings</Drawer.Title>
          <Drawer.Description>
            Update how this project deploys.
          </Drawer.Description>

          <Drawer.Close render={<Button>Save changes</Button>} />
        </Drawer.Content>
      </Drawer.Popup>
    </Drawer.Viewport>
  </Drawer.Portal>
</Drawer.Root>

Controlled

Leave the drawer uncontrolled and let Drawer.Trigger own the open state, or drive it yourself: pass open and onOpenChange to Drawer.Root to open it from anywhere — a menu item, a keyboard shortcut, a completed request. A controlled drawer needs no Drawer.Trigger; when it closes, focus returns to whatever was focused before it opened.

Panel is closed
const [open, setOpen] = useState(false);

<Button variant="outlined" onClick={() => setOpen(true)}>
  Open panel
</Button>
<Drawer.Root open={open} onOpenChange={setOpen}>
  <Drawer.Portal>…</Drawer.Portal>
</Drawer.Root>

Anatomy

Props

Drawer.Root owns the behavior:

Drawer.Popup refines focus:

Every styled part (Backdrop, Viewport, Popup, Content, Title, Description) takes className to extend its styling; all parts accept Base UI’s render prop for swapping the underlying element. Advanced Drawer.Root options — imperative actionsRef, detached handle, and bottom-sheet snapPoints — pass straight through to Base UI.

Accessibility

Keyboard:

Best practices