

<Preview name="BasicDialogExample" />

## Overview [#overview]

The **Dialog** component is a modal window that displays content on top of the main application, requiring user interaction before continuing. Built on Base UI primitives, it features smooth animations, backdrop blur effects, sticky headers and footers for scrollable content, and flexible sizing options including fullscreen mode.

## Usage [#usage]

```tsx
import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogBody,
  DialogFooter,
  DialogClose,
  DialogAction,
} from "@tilt-legal/cubitt-components/primitives";
```

```tsx
<Dialog>
  <DialogTrigger render={<Button />}>Open Dialog</DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Dialog Title</DialogTitle>
      <DialogDescription>Dialog description goes here.</DialogDescription>
    </DialogHeader>
  </DialogContent>
</Dialog>
```

<Accordions type="single">
  <Accordion title="URL State Management">
    Using Cubitt's URL-state hooks, you can sync the dialog state with the URL by providing a `paramName`:

    ```tsx
    // Opens when ?settings=true in URL
    <Dialog paramName="settings">
      <DialogTrigger render={<Button />}>Open Settings</DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Settings</DialogTitle>
          <DialogDescription>
            Configure your preferences.
          </DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>

    // Advanced options:
    <Dialog
      paramName="dialog"
      paramClearOnDefault={true}    // Remove param when closed
      onUrlValueChange={(value) => console.log('Dialog open:', value)}
    >
      {/* ... */}
    </Dialog>
    ```

    **Using String Values (e.g., IDs)**

    Dialog URL state always serializes to strings. There are two modes:

    **Mode 1: Open for Any Value** (no `paramMatchValue` specified)

    ```tsx
    // Opens when ANY value is present: ?dialog=true, ?dialog=abc, etc.
    // Useful when you just need to track that dialog was opened via URL
    <Dialog
      paramName="dialog"
      onUrlValueChange={(value) => {
        console.log("URL value:", value); // Gets the actual string value
      }}
    >
      <DialogContent>{/* ... */}</DialogContent>
    </Dialog>
    ```

    **Mode 2: Match Specific Value** (with `paramMatchValue`)

    This mode is useful when you have multiple dialogs and want each to respond to a specific ID:

    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";

    // Button sets item ID
    <Button paramName="inspect-dialog" paramSetValue="item-001">
      Inspect Item 001
    </Button>

    // Dialog opens ONLY when ?inspect-dialog=item-001
    <Dialog
      paramName="inspect-dialog"
      paramMatchValue="item-001"  // ← Must match this specific value
    >
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Inspector: Item 001</DialogTitle>
          <DialogDescription>
            This dialog only opens for item-001!
          </DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>
    ```

    This allows multiple dialogs with different `paramMatchValue` props to coexist - each only opens when the URL matches their specific value.
  </Accordion>
</Accordions>

## Examples [#examples]

### Dialog with Form [#dialog-with-form]

Dialog containing form inputs for data entry.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DialogWithFormExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Dialog>
      <DialogTrigger render={<Button />}>Edit Profile</DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Edit Profile</DialogTitle>
          <DialogDescription>
            Make changes to your profile here. Click save when you're done.
          </DialogDescription>
        </DialogHeader>
        <DialogBody>
          <div className="flex flex-col gap-4">
            <div className="flex flex-col gap-2">
              <Label htmlFor="name">Name</Label>
              <Input id="name" defaultValue="John Doe" />
            </div>
            <div className="flex flex-col gap-2">
              <Label htmlFor="email">Email</Label>
              <Input id="email" type="email" defaultValue="john@example.com" />
            </div>
          </div>
        </DialogBody>
        <DialogFooter>
          <DialogClose>Cancel</DialogClose>
          <DialogAction>Save Changes</DialogAction>
        </DialogFooter>
      </DialogContent>
    </Dialog>
    ```
  </Tab>
</Tabs>

### Scrollable Content [#scrollable-content]

Dialog with long content that scrolls. The header and footer remain sticky with a blurred background effect.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DialogWithScrollableContentExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Dialog>
      <DialogTrigger render={<Button />}>Terms & Conditions</DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Terms and Conditions</DialogTitle>
          <DialogDescription>
            Please read and accept our terms and conditions.
          </DialogDescription>
        </DialogHeader>
        <DialogBody>
          <div className="space-y-4">{/* Long content... */}</div>
        </DialogBody>
        <DialogFooter>
          <DialogClose>Decline</DialogClose>
          <DialogAction>Accept</DialogAction>
        </DialogFooter>
      </DialogContent>
    </Dialog>
    ```
  </Tab>
</Tabs>

The scrollbar track is automatically inset to clear the sticky header and footer areas. This is controlled via CSS custom properties `--scrollbar-inset-top` and `--scrollbar-inset-bottom`, which default to `3.5rem` inside `DialogContent`. You can override these per-dialog if your header or footer has a non-standard height:

```tsx
<DialogContent className="[--scrollbar-inset-top:5rem] [--scrollbar-inset-bottom:4rem]">
  {/* ... */}
</DialogContent>
```

### Fullscreen Dialog [#fullscreen-dialog]

Dialog that takes up most of the screen, useful for complex forms or detailed content.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="FullscreenDialogExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Dialog>
      <DialogTrigger render={<Button />}>Open Fullscreen Dialog</DialogTrigger>
      <DialogContent fullscreen>
        <DialogHeader>
          <DialogTitle>Fullscreen Dialog</DialogTitle>
          <DialogDescription>
            This dialog takes up most of the screen space.
          </DialogDescription>
        </DialogHeader>
        <DialogBody>
          <p>Content goes here...</p>
        </DialogBody>
        <DialogFooter>
          <DialogClose>Close</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
    ```
  </Tab>
</Tabs>

### Without Dismiss Button [#without-dismiss-button]

Dialog without the X close button in the top right corner.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DialogWithoutDismissButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Dialog>
      <DialogTrigger render={<Button />}>Open Without Dismiss Button</DialogTrigger>
      <DialogContent showDismissButton={false}>
        <DialogHeader>
          <DialogTitle>No Dismiss Button</DialogTitle>
          <DialogDescription>
            This dialog doesn't have the X close button in the top right.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose>Close</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
    ```
  </Tab>
</Tabs>

### Nested Dialogs [#nested-dialogs]

Open a dialog from within another dialog. The parent dialog will scale down and shift to create a visual hierarchy.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="NestedDialogExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Dialog>
      <DialogTrigger render={<Button />}>Manage Team Member</DialogTrigger>
      <DialogContent showDismissButton={false}>
        <DialogHeader>
          <DialogTitle>Manage Team Member</DialogTitle>
          <DialogDescription>
            View and manage a user in your team.
          </DialogDescription>
        </DialogHeader>
        <DialogBody>
          <div className="grid gap-4">
            <div className="grid gap-1">
              <p className="text-muted-foreground text-sm">Name</p>
              <p className="font-medium text-sm">Sarah Anderson</p>
            </div>
            <div className="grid gap-1">
              <p className="text-muted-foreground text-sm">Email</p>
              <p className="font-medium text-sm">sarah.anderson@company.com</p>
            </div>
          </div>
        </DialogBody>
        <DialogFooter>
          {/* Nested Dialog */}
          <Dialog>
            <DialogTrigger render={<Button variant="secondary" />}>
              Edit Details
            </DialogTrigger>
            <DialogContent showDismissButton={false}>
              <DialogHeader>
                <DialogTitle>Edit Details</DialogTitle>
                <DialogDescription>
                  Make changes to the member's information.
                </DialogDescription>
              </DialogHeader>
              <DialogBody>
                <div className="flex flex-col gap-4">
                  <div className="flex flex-col gap-1">
                    <Label htmlFor="name">Name</Label>
                    <Input defaultValue="Sarah Anderson" id="name" />
                  </div>
                  <div className="flex flex-col gap-1">
                    <Label htmlFor="email">Email</Label>
                    <Input defaultValue="sarah.anderson@company.com" id="email" />
                  </div>
                </div>
              </DialogBody>
              <DialogFooter>
                <DialogClose>Cancel</DialogClose>
                <DialogAction>Save Changes</DialogAction>
              </DialogFooter>
            </DialogContent>
          </Dialog>
          <DialogClose>Close</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
    ```
  </Tab>
</Tabs>

### URL State [#url-state]

Dialog with URL state synchronization for shareable dialog states.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview" className="p-0">
    <Preview name="DialogURLStateExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Dialog,
      DialogAction,
      DialogClose,
      DialogContent,
      DialogDescription,
      DialogFooter,
      DialogHeader,
      DialogTitle,
      Button,
    } from "@tilt-legal/cubitt-components/primitives";

    // Button sets URL param directly, Dialog reads it
    <>
      <Button variant="outline" paramName="settings" paramSetValue="true">
        Open Settings
      </Button>

      <Dialog paramName="settings">
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Settings</DialogTitle>
            <DialogDescription>
              This dialog's state is synced with the URL. Check the URL parameter
              when you open it!
            </DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <DialogClose>Cancel</DialogClose>
            <DialogAction>Save</DialogAction>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>;
    ```
  </Tab>
</Tabs>

***

## API Reference [#api-reference]

### Dialog [#dialog]

Root component that manages dialog state.

| Prop           | Type                      | Default | Description                                 |
| -------------- | ------------------------- | ------- | ------------------------------------------- |
| `open`         | `boolean`                 | -       | Controlled open state of the dialog.        |
| `defaultOpen`  | `boolean`                 | `false` | Default open state for uncontrolled usage.  |
| `onOpenChange` | `(open: boolean) => void` | -       | Callback fired when the open state changes. |
| `modal`        | `boolean`                 | `true`  | Whether the dialog is modal (locks scroll). |
| `disabled`     | `boolean`                 | `false` | Whether the dialog is disabled.             |

#### URL State Props [#url-state-props]

When provided with a `paramName`, the dialog will sync its open/closed state with URL parameters via TanStack Router search params.

| Prop                  | Type                      | Default | Description                                                                  |
| --------------------- | ------------------------- | ------- | ---------------------------------------------------------------------------- |
| `paramName`           | `string`                  | —       | URL parameter name for syncing state with URL. Enables URL state management. |
| `paramMatchValue`     | `string`                  | —       | Value that must match for the dialog to open.                                |
| `onUrlValueChange`    | `(value: string) => void` | —       | Callback when URL parameter value changes.                                   |
| `paramClearOnDefault` | `boolean`                 | `true`  | Remove URL param when dialog is closed.                                      |
| `paramThrottle`       | `number`                  | —       | Throttle URL updates in milliseconds.                                        |
| `paramDebounce`       | `number`                  | —       | Debounce URL updates in milliseconds.                                        |

### DialogTrigger [#dialogtrigger]

Button that opens the dialog.

| Prop       | Type                 | Default | Description                       |
| ---------- | -------------------- | ------- | --------------------------------- |
| `render`   | `React.ReactElement` | -       | Custom trigger element to render. |
| `disabled` | `boolean`            | `false` | Whether the trigger is disabled.  |

### DialogContent [#dialogcontent]

Container for the dialog content with backdrop and animations.

| Prop                | Type      | Default | Description                                                                   |
| ------------------- | --------- | ------- | ----------------------------------------------------------------------------- |
| `showBackdrop`      | `boolean` | `true`  | Whether to show the backdrop overlay.                                         |
| `showDismissButton` | `boolean` | `true`  | Whether to show the X close button in the top right.                          |
| `fullscreen`        | `boolean` | `false` | Whether the dialog should take up most of the screen.                         |
| `nestedOffset`      | `boolean` | `true`  | Whether to apply offset transforms for nested dialogs based on nesting depth. |
| `className`         | `string`  | -       | Additional CSS classes for the dialog popup.                                  |

#### CSS Custom Properties [#css-custom-properties]

| Variable                   | Default   | Description                                                 |
| -------------------------- | --------- | ----------------------------------------------------------- |
| `--scrollbar-inset-top`    | `3.75rem` | Top margin of the scrollbar track (clears sticky header)    |
| `--scrollbar-inset-bottom` | `4rem`    | Bottom margin of the scrollbar track (clears sticky footer) |

### DialogHeader [#dialogheader]

Container for dialog title and description with sticky positioning.

| Prop        | Type     | Default | Description                            |
| ----------- | -------- | ------- | -------------------------------------- |
| `className` | `string` | -       | Additional CSS classes for the header. |

### DialogTitle [#dialogtitle]

The accessible title of the dialog.

| Prop        | Type     | Default | Description                           |
| ----------- | -------- | ------- | ------------------------------------- |
| `className` | `string` | -       | Additional CSS classes for the title. |

### DialogDescription [#dialogdescription]

Description text that provides context about the dialog.

| Prop        | Type     | Default | Description                                 |
| ----------- | -------- | ------- | ------------------------------------------- |
| `className` | `string` | -       | Additional CSS classes for the description. |

### DialogBody [#dialogbody]

Container for the main dialog content.

| Prop        | Type     | Default | Description                          |
| ----------- | -------- | ------- | ------------------------------------ |
| `className` | `string` | -       | Additional CSS classes for the body. |

### DialogFooter [#dialogfooter]

Container for dialog actions with sticky positioning.

| Prop        | Type     | Default | Description                            |
| ----------- | -------- | ------- | -------------------------------------- |
| `className` | `string` | -       | Additional CSS classes for the footer. |

### DialogClose [#dialogclose]

Button that closes the dialog (styled as outline button by default).

| Prop        | Type                 | Default | Description                                  |
| ----------- | -------------------- | ------- | -------------------------------------------- |
| `render`    | `React.ReactElement` | -       | Custom close button element to render.       |
| `className` | `string`             | -       | Additional CSS classes for the close button. |

### DialogAction [#dialogaction]

Primary action button that closes the dialog (styled as default button).

| Prop        | Type                 | Default | Description                                   |
| ----------- | -------------------- | ------- | --------------------------------------------- |
| `render`    | `React.ReactElement` | -       | Custom action button element to render.       |
| `className` | `string`             | -       | Additional CSS classes for the action button. |
