

# Dialog

Modal overlay for focused tasks. `Dialog` is the compound Root (`Dialog.Trigger`, `Dialog.Backdrop`, `Dialog.Popup`, and chrome parts). The trigger child must forward DOM props (a `Button` or a host element) — there is no `render` prop. For the previous `triggerLabel` / `title` / `description` API, use the `SimpleDialog` preset.

There is no `@var-ui/astro` Dialog — Astro and HTML previews show a static core-recipe approximation of the open chrome; full interaction is React-only.

You can nest `Tooltip` parts around `Dialog.Trigger` (Tooltip wraps the trigger, both stay inside `Dialog`) without a `render` prop.

State attributes, exit animation, and `onOpenChange` details are documented in [Overlay lifecycle](/docs/overlay-lifecycle).

## Examples

### Compound parts

Compose Trigger, Backdrop, Popup, and chrome. `Dialog` is Root.

```tsx
import { Button, Dialog } from '@var-ui/react';

<Dialog>
  <Dialog.Trigger>
    <Button intent="secondary">Open dialog</Button>
  </Dialog.Trigger>
  <Dialog.Backdrop>
    <Dialog.Popup>
      <Dialog.Header>
        <Dialog.Title>Notifications</Dialog.Title>
        <Dialog.Close aria-label="Close" />
      </Dialog.Header>
      <Dialog.Description>You are all caught up.</Dialog.Description>
      <Dialog.Actions>
        <Dialog.Close>
          <Button>Close</Button>
        </Dialog.Close>
      </Dialog.Actions>
    </Dialog.Popup>
  </Dialog.Backdrop>
</Dialog>
```

```astro
---
// No @var-ui/astro Dialog — static core-recipe chrome (open state, non-interactive).
// Full open/close interaction is React-only via react-aria-components.
import { button, dialog, resolveButtonProps } from '@var-ui/core';

const d = dialog();
const trigger = button(resolveButtonProps({ intent: 'secondary' }));
const closeAction = button({});
---

<div style="display: grid; gap: 1rem">
  <button type="button" class:list={[trigger]} disabled>Open dialog</button>
  <div class:list={[d.overlay]} style="position: relative; inset: auto; min-height: 220px">
    <div class:list={[d.modal]} role="dialog" aria-labelledby="dialog-demo-title" aria-modal="true">
      <div class:list={[d.content]}>
        <div class:list={[d.header]}>
          <h2 id="dialog-demo-title" class:list={[d.heading]}>Icon close button</h2>
          <button type="button" class:list={[d.closeButton]} aria-label="Close" disabled>×</button>
        </div>
        <p class:list={[d.description]}>The dismiss control now uses the registry close glyph.</p>
        <button type="button" class:list={[closeAction]} disabled>Close</button>
      </div>
    </div>
  </div>
</div>
```

```html
<div style="display: grid; gap: 1rem"><button type="button" data-tone="neutral" data-appearance="subtle" data-size="md" data-layout="default" class="var-ui-button" disabled>Open dialog</button><div data-role="dialog" class="var-ui-dialog__overlay" style="position: relative; inset: auto; min-height: 220px"><div data-role="dialog" class="var-ui-dialog__modal" role="dialog" aria-labelledby="dialog-demo-title" aria-modal="true"><div data-role="dialog" class="var-ui-dialog__content"><div data-role="dialog" class="var-ui-dialog__header"><h2 id="dialog-demo-title" data-role="dialog" class="var-ui-dialog__heading">Icon close button</h2><button type="button" data-role="dialog" class="var-ui-dialog__closeButton" aria-label="Close" disabled><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"></path></svg></button></div><p data-role="dialog" class="var-ui-dialog__description">The dismiss control now uses the registry close glyph.</p><button type="button" data-tone="neutral" data-appearance="subtle" data-size="md" data-layout="default" class="var-ui-button" disabled>Close</button></div></div></div></div>
```

### SimpleDialog preset

`SimpleDialog` keeps the assembled 80% case copy-pasteable.

```tsx
import { SimpleDialog } from '@var-ui/react';

<SimpleDialog
  triggerLabel="Open dialog"
  title="Notifications"
  description="You are all caught up."
/>
```

```astro
<!-- No Astro binding yet — use @var-ui/react -->
```

```html
<!-- No HTML demo yet — use @var-ui/react -->
```

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | ReactNode | no |  |  |
| defaultOpen | boolean \| undefined | no |  |  |
| isDismissable | boolean \| undefined | no | true | RAC: isDismissable on the overlay. |
| isOpen | boolean \| undefined | no |  |  |
| onOpenChange | OverlayOpenChangeHandler \| undefined | no |  |  |
| portalContainer | Element \| undefined | no |  | Element the modal portals into instead of `document.body`. Needed when a subtree renders under a different theme than the page ambient (the theme's CSS custom properties only cascade to descendants of the themed element). |

## Accessibility

Built on React Aria Components where applicable. Focus moves into the dialog; Escape and the backdrop dismiss when `isDismissable` is true.
