# Dialog

- Page: <https://kznlabs.github.io/labsUI/components/dialog>

## Dialog (`dialog`, component)

Portal modal with blurred backdrop, spring scale-in, focus trap, Esc and scroll lock.

- Install: `npx @kznlabs/labsui add dialog`
- Docs: <https://kznlabs.github.io/labsUI/components/dialog>
- Brings: button, button-variants, platform, use-controllable-state, utils
- npm: clsx@^2.1.1, lucide-react@1.63.0, motion@14.7.1, tailwind-merge@^3.7.0

### API

- `useDialog() => Ctx`
- `<Dialog>`: State root. Controlled (`open` + `onOpenChange`) or uncontrolled.
  - `children: ReactNode`: `DialogTrigger` and `DialogContent`.
  - `defaultOpen?: boolean` = `false`: Open at first render (uncontrolled).
  - `onOpenChange?: (open: boolean) => void`: Called on the trigger, Esc, backdrop click or `DialogClose`.
  - `open?: boolean`: Controlled state; pair with `onOpenChange`.
- `<DialogTrigger>` (+ Button props)
  - `children?: ReactNode`: Label; its width is reserved so loading and success never resize the button.
  - `confirmLabel?: ReactNode`: Two-step action: first click swaps the label to this, second click runs `onClick`.
  - `confirmTimeout?: number`: Ms before an armed confirm step resets. Also resets on blur or Esc.
  - `loading?: boolean`: Disables the button and shows a spinner before the label.
  - `loadingLabel?: ReactNode`: Label shown while loading, with the spinner. Defaults to `children`.
  - `size?: "default" | "sm" | "lg" | "icon"`: Height and padding; "icon" is square, so give it an `aria-label`.
  - `status?: ButtonStatus`: In-place feedback: `loading` spins, `success` swaps the label to `successLabel`.
  - `successLabel?: ReactNode`: Text shown on `status="success"`, with a check icon. Defaults to "Done".
  - `swap?: SwapDirection`: Where the old label slides out to on a swap. Default "left".
  - `variant?: "default" | "secondary" | "outline" | "ghost" | "destructive"`: Fill and emphasis: one primary action per view, `destructive` for irreversible ones.
- `<DialogClose>` (+ Button props)
  - `children?: ReactNode`: Label; its width is reserved so loading and success never resize the button.
  - `confirmLabel?: ReactNode`: Two-step action: first click swaps the label to this, second click runs `onClick`.
  - `confirmTimeout?: number`: Ms before an armed confirm step resets. Also resets on blur or Esc.
  - `loading?: boolean`: Disables the button and shows a spinner before the label.
  - `loadingLabel?: ReactNode`: Label shown while loading, with the spinner. Defaults to `children`.
  - `size?: "default" | "sm" | "lg" | "icon"`: Height and padding; "icon" is square, so give it an `aria-label`.
  - `status?: ButtonStatus`: In-place feedback: `loading` spins, `success` swaps the label to `successLabel`.
  - `successLabel?: ReactNode`: Text shown on `status="success"`, with a check icon. Defaults to "Done".
  - `swap?: SwapDirection`: Where the old label slides out to on a swap. Default "left".
  - `variant?: "default" | "secondary" | "outline" | "ghost" | "destructive"` = `"outline"`: Fill and emphasis: one primary action per view, `destructive` for irreversible ones.
- `useModal() => RefObject<HTMLDivElement | null>`: Modal behavior shared by Dialog and Drawer: focus first field, trap Tab, restore focus,
lock page scroll, close on Esc (topmost modal only). Attach the returned ref to the panel.
- `<DialogContent>`: Portal + blurred backdrop + spring scale-in. Esc/backdrop close, focus trap, scroll lock.
  - `children: ReactNode`: Header, body and footer.
  - `alert?: boolean` = `false`: Confirm-a-destructive-action mode: role="alertdialog", backdrop click does not close.
  - `showClose?: boolean` = `!alert`: Close button in the corner. Off by default for `alert` dialogs.
- `<DialogHeader>` (+ <div> props)
- `<DialogFooter>` (+ <div> props)
- `<DialogTitle>` (+ <h2> props)
- `<DialogDescription>` (+ <p> props)

- `type Ctx = { open: boolean; setOpen: (open: boolean) => void; titleId: string; descId: string }`

### Usage

```tsx
<Dialog><DialogTrigger>Open</DialogTrigger><DialogContent><DialogHeader><DialogTitle>…</DialogTitle></DialogHeader></DialogContent></Dialog>
```

### Example: edit-profile.tsx

```tsx
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
export function EditProfile() {
  return (
    <Dialog>
      <DialogTrigger variant="outline">Edit profile</DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Edit profile</DialogTitle>
          <DialogDescription>Changes apply to your account.</DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose>Cancel</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
```
