# Dialog

> A modal window for focused tasks that must be finished or dismissed before returning to the page.

Category: Overlays · Status: stable · Package: @bindshq/stance-react@0.1.1 · Docs: https://stanceui.com/components/dialog

Use Dialog for short, self-contained tasks such as renaming a template or inviting a colleague. It traps focus, locks page scroll and returns focus to the trigger when closed. For confirming destructive actions use AlertDialog; for long or secondary content use Drawer; for non-blocking content use Popover. On phones the dialog becomes a bottom sheet automatically.

## Import

```tsx
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, DialogClose } from '@bindshq/stance-react';
```

Styles are imported once per app: `@bindshq/stance-tokens/css` and `@bindshq/stance-react/styles.css`.

## Examples

### Basic

```tsx
import { Button, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@bindshq/stance-react';

export function Basic() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="outline">View deployment details</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Deployment details</DialogTitle>
          <DialogDescription>
            Signatures were pushed to 128 mailboxes through Exchange Online.
          </DialogDescription>
        </DialogHeader>
        <DialogBody>
          <p style={{ margin: 0 }}>
            The next automatic sync runs tonight at 02:00. Mailboxes added after that will receive
            the template on the following run.
          </p>
        </DialogBody>
        <DialogFooter>
          <DialogClose asChild>
            <Button>Done</Button>
          </DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
```

### With a form

Focus moves to the first field. Submitting closes the dialog.

```tsx
import * as React from 'react';
import { Button, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, FormField, Input } from '@bindshq/stance-react';

export function WithForm() {
  const [open, setOpen] = React.useState(false);
  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>
        <Button>Rename template</Button>
      </DialogTrigger>
      <DialogContent size="sm">
        <form
          onSubmit={(e) => {
            e.preventDefault();
            setOpen(false);
          }}
        >
          <DialogHeader>
            <DialogTitle>Rename template</DialogTitle>
            <DialogDescription>
              Names are visible to everyone in your organisation.
            </DialogDescription>
          </DialogHeader>
          <DialogBody>
            <FormField label="Template name" required>
              <Input defaultValue="Sales, EMEA" />
            </FormField>
          </DialogBody>
          <DialogFooter>
            <DialogClose asChild>
              <Button variant="outline">Cancel</Button>
            </DialogClose>
            <Button type="submit">Save</Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  );
}
```

### Sizes

sm 400px, md 520px, lg 720px, xl 960px, or full screen.

```tsx
import { Button, Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@bindshq/stance-react';

export function Sizes() {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12 }}>
      {(['sm', 'md', 'lg', 'xl'] as const).map((size) => (
        <Dialog key={size}>
          <DialogTrigger asChild>
            <Button variant="outline">Open {size}</Button>
          </DialogTrigger>
          <DialogContent size={size}>
            <DialogHeader>
              <DialogTitle>Size “{size}”</DialogTitle>
            </DialogHeader>
            <DialogBody>Content wraps to the dialog width and scrolls when tall.</DialogBody>
          </DialogContent>
        </Dialog>
      ))}
    </div>
  );
}
```

### Controlled

Drive open state yourself, e.g. after an async check.

```tsx
import * as React from 'react';
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@bindshq/stance-react';

export function Controlled() {
  const [open, setOpen] = React.useState(false);
  return (
    <>
      <Button variant="secondary" onClick={() => setOpen(true)}>
        Open programmatically
      </Button>
      <Dialog open={open} onOpenChange={setOpen}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Session expiring</DialogTitle>
            <DialogDescription>
              You will be signed out in two minutes for security.
            </DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button variant="outline" onClick={() => setOpen(false)}>
              Sign out
            </Button>
            <Button onClick={() => setOpen(false)}>Stay signed in</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );
}
```

## API reference

| Component | Prop | Type | Default | Description |
| --- | --- | --- | --- | --- |
| Dialog | `open` | `boolean` | None | Controlled open state. |
| Dialog | `defaultOpen` | `boolean` | `false` | Initial state when uncontrolled. |
| Dialog | `onOpenChange` | `(open: boolean) => void` | None | Called when the dialog opens or closes. |
| DialogTrigger | `asChild` | `boolean` | `false` | Render your own element (e.g. a Button) as the trigger. |
| DialogContent | `size` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | Maximum width. |
| DialogContent | `initialFocus` | `RefObject<HTMLElement>` | None | Element focused on open. Defaults to the first focusable element. |
| DialogContent | `showClose` | `boolean` | `true` | Show the top-right close button. |
| DialogContent | `closeLabel` | `string` | `'Close'` | Accessible label for the close button. |
| DialogContent | `dismissOnOutsideClick` | `boolean` | `true` | Close when the backdrop is clicked. |
| DialogContent | `restoreFocus` | `boolean` | `true` | Return focus to the trigger on close. |
| DialogContent | `onEscapeKeyDown` | `(event: KeyboardEvent) => void` | None | Call event.preventDefault() to keep the dialog open. |
| DialogContent | `onPointerDownOutside` | `(event: PointerEvent) => void` | None | Call event.preventDefault() to keep the dialog open. |
| DialogContent | `disablePortal` | `boolean` | `false` | Render inline instead of into document.body. |
| DialogTitle | `level` | `1 \| 2 \| 3 \| 4` | `2` | Heading level. The title is the dialog’s accessible name and is required. |
| DialogDescription | `...props` | `HTMLAttributes<HTMLParagraphElement>` | None | Linked to the dialog via aria-describedby. |
| DialogHeader / DialogBody / DialogFooter | `...props` | `HTMLAttributes<HTMLDivElement>` | None | Layout regions. The body scrolls when content is tall. |
| DialogClose | `asChild` | `boolean` | `false` | Wrap your own Button to close the dialog. |

## Accessibility

- The surface has role="dialog" and aria-modal="true", named by DialogTitle and described by DialogDescription.
- Focus moves into the dialog on open, is trapped while open, and returns to the trigger on close.
- Page scroll is locked while open; nested overlays (menus, popovers) keep working inside the dialog.
- The trigger exposes aria-haspopup="dialog", aria-expanded and aria-controls.
- Escape always closes the top-most layer only.

## Keyboard

| Keys | Action |
| --- | --- |
| `Tab` | Moves to the next focusable element inside the dialog, wrapping at the end. |
| `Shift` `Tab` | Moves to the previous focusable element, wrapping at the start. |
| `Esc` | Closes the dialog and returns focus to the trigger. |

## Guidelines

**Do**

- Give every dialog a clear title that states the task.
- Keep dialogs short; move long flows to a page or Drawer.
- Put the primary action last in the footer.

**Don’t**

- Open a dialog from another dialog.
- Use a dialog for messages that need no decision; use Toast or Alert.

## Related

- https://stanceui.com/components/alert-dialog
- https://stanceui.com/components/confirmation-dialog
- https://stanceui.com/components/drawer
- https://stanceui.com/components/sheet
- https://stanceui.com/components/popover
