> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dialog - Web

> Dialogs display content that temporarily blocks interactions within the main view of an interface.

> [!NOTE]
>
> For development, import `Dialog` from
> `@servicetitan/anvil2`. For design, find it in the
> [Anvil2 file](https://www.figma.com/design/xaNQsvRKczoxu7pj0M6QT8/Anvil2-v3?node-id=59-2632).

![](https://mintcdn.com/servicetitan/uz2PQSvO75TRhQ38/images/docs/web/components/shared/dialog-overview.png?fit=max\&auto=format\&n=uz2PQSvO75TRhQ38\&q=85\&s=9f61f5018d7c00d021f25afdfb70cf06)

## Anatomy

The Dialog consists of five primary elements that work together to temporarily block interactions within the main view.

![Anatomy of a
Dialog](https://mintcdn.com/servicetitan/lX6UhRsjMdTeB7y6/images/docs/web/components/dialog/design/anatomy-of-a-dialog.png?fit=max\&auto=format\&n=lX6UhRsjMdTeB7y6\&q=85\&s=0c3c39b3e3be580c6dcd431d341578aa)

1. Header
2. Body content
3. Backdrop
4. Close action
5. Footer actions

## Options

The Dialog supports flexible content, footer actions, and sizing configurations to accommodate various modal scenarios.

### Body content

dialog-content

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-content?file=src/App.tsx)

```tsx lines theme={null}
import {
  Dialog,
  Grid,
  Text,
  TextField,
  Radio,
  Flex,
  Button,
} from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "23rem", minHeight: "37rem" }}>
      <Dialog open>
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>
          <Grid gap="6" flexGrow="1">
            <Text>
              Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
              adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac,
              malesuada ac nisi.
            </Text>

            <TextField label="Label" />
            <TextField label="Label" />

            <Radio.Group legend="Radio group header">
              <Radio name="role" value="designer" label="Apple" />
              <Radio name="role" value="developer" label="Banana" />
              <Radio name="role" value="pm" label="Carrot" />
            </Radio.Group>
          </Grid>
        </Dialog.Content>
        <Dialog.Footer>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="primary">Primary Action</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

Add any type of content into the body of a Dialog.

### Footer actions

dialog-footer

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-footer?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "35rem", minHeight: "12rem" }}>
      <Dialog open size="large">
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>Body text in the Dialog.</Dialog.Content>
        <Dialog.Footer>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="primary">Primary Action</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

dialog-footer-button-left

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-footer-button-left?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "35rem", minHeight: "12rem" }}>
      <Dialog open size="large">
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>Body text in the Dialog.</Dialog.Content>
        <Dialog.Footer>
          <Button>Back</Button>
          {/* spacer */}
          <Flex grow="1" />
          <Dialog.CancelButton>Cancel</Dialog.CancelButton>
          <Button appearance="primary">Next</Button>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

dialog-footer-link-left

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-footer-link-left?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Link, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "35rem", minHeight: "12rem" }}>
      <Dialog open size="large">
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>Body text in the Dialog.</Dialog.Content>
        <Dialog.Footer style={{ flexWrap: "wrap", alignItems: "center" }}>
          <Link>Learn more</Link>
          <Flex grow="1" />
          <Flex wrap="wrap" justifyContent="flex-end" gap="2">
            <Dialog.CancelButton>Cancel</Dialog.CancelButton>
            <Button appearance="primary">Primary Action</Button>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

dialog-footer-button-danger

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-footer-button-danger?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "35rem", minHeight: "12rem" }}>
      <Dialog open size="large">
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>Body text in the Dialog.</Dialog.Content>
        <Dialog.Footer>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="danger">Destructive Action</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

### Sizing

The Dialog has 4 sizing options: medium (default), large, extra large, and fullscreen.

#### Default

dialog-size-medium

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-size-medium?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "23rem", minHeight: "16rem" }}>
      <Dialog open>
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
        </Dialog.Content>
        <Dialog.Footer>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="primary">Primary Action</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

#### Large

dialog-size-large

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-size-large?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "35rem", minHeight: "15rem" }}>
      <Dialog open size="large">
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
        </Dialog.Content>
        <Dialog.Footer>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="primary">Primary Action</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

#### Extra Large

dialog-size-xlarge

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-size-xlarge?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "70rem", minHeight: "13rem" }}>
      <Dialog open size="xlarge">
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
        </Dialog.Content>
        <Dialog.Footer>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="primary">Primary Action</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

#### Fullscreen

dialog-size-fullscreen-full-page

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-size-fullscreen-full-page?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div>
      <Dialog open fullScreen>
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
        </Dialog.Content>
        <Dialog.Footer>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="primary">Primary Action</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

### Sizing table

| Sizing | Width | Height | Max Height |
| - | - | - | - |
| Medium (default) | 22.5rem / 360px | auto | 100% - (1rem \* 2) |
| Large | 35rem / 560px | auto | 100% - (1rem \* 2) |
| Extra Large | 70rem / 1120px | auto | 100% - (1rem \* 2) |
| Fullscreen | 100% | 100% | – |

## Behavior

The Dialog responds to content overflow and scrolling requirements while maintaining focus management.

### Overflow handling

Content in the Dialog will wrap, rather than truncate. Header content will avoid colliding with the close action, while footer actions will wrap to the right, with the right-most actions being at the bottom. Footer actions can have a custom overflow behavior if needed.

dialog-footer-overflow

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-footer-overflow?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Link, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "23rem", minHeight: "12rem" }}>
      <Dialog open>
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>Body text in the Dialog.</Dialog.Content>
        <Dialog.Footer style={{ flexWrap: "wrap", alignItems: "center" }}>
          <Link>Learn more</Link>
          <Flex grow="1" />
          <Flex wrap="wrap" justifyContent="flex-end" gap="2">
            <Dialog.CancelButton>Cancel</Dialog.CancelButton>
            <Button appearance="primary">Primary</Button>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

dialog-footer-overflow-wrap-link

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-footer-overflow-wrap-link?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Link, Flex, Button } from "@servicetitan/anvil2";
import { core } from "@servicetitan/anvil2/token";

function App() {
  return (
    <div style={{ minWidth: "23rem", minHeight: "15rem" }}>
      <Dialog open>
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>Body text in the Dialog.</Dialog.Content>
        <Dialog.Footer style={{ flexWrap: "wrap" }}>
          <Link
            style={{
              marginTop: core.primitive?.Size2?.value,
              marginBottom: core.primitive?.Size2?.value,
            }}
          >
            Learn more
          </Link>
          <Flex wrap="wrap" justifyContent="flex-end" gap="2">
            <Dialog.CancelButton>Cancel</Dialog.CancelButton>
            <Button appearance="primary">Primary Action</Button>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

dialog-footer-overflow-wrap-button

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-footer-overflow-wrap-button?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Link, Flex, Button } from "@servicetitan/anvil2";
import { core } from "@servicetitan/anvil2/token";

function App() {
  return (
    <div style={{ minWidth: "23rem", minHeight: "18rem" }}>
      <Dialog open>
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>Body text in the Dialog.</Dialog.Content>
        <Dialog.Footer style={{ flexWrap: "wrap" }}>
          <Link
            style={{
              marginTop: core.primitive?.Size2?.value,
              marginBottom: core.primitive?.Size2?.value,
            }}
          >
            Learn more
          </Link>
          <Flex wrap="wrap" justifyContent="flex-end" gap="2">
            <Dialog.CancelButton>A Longer Secondary Action</Dialog.CancelButton>
            <Button appearance="primary">Primary Action</Button>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

### Scrolling

The body region of the Dialog will scroll when space is not available. The Dialog's height will fill the height of the browser window (minus 1 rem top and bottom margins), and scroll past that point.

dialog-scrolling

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-scrolling?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "23rem", minHeight: "37rem" }}>
      <Dialog open>
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit
          amet est eget est dapibus molestie. Phasellus ornare egestas turpis,
          sit amet auctor ligula pharetra non. Duis et tellus vel lacus
          pellentesque semper. Integer tempor, sapien maximus auctor
          pellentesque, felis nibh vehicula justo, ut consequat mauris sem a
          justo. Donec rutrum, velit bibendum condimentum ornare, eros justo
          sagittis metus, eget blandit sapien lectus eget lacus. Proin non arcu
          maximus, gravida odio et, faucibus turpis. Nam venenatis, elit at
          ultricies aliquet, nunc mi bibendum risus, et maximus tellus purus a
          eros. Aliquam vitae pulvinar velit, a accumsan magna. Maecenas aliquet
          varius augue, vitae fermentum magna suscipit et. Curabitur fringilla
          arcu sit amet lorem porttitor, vel mattis ligula dapibus. Vivamus et
          ornare quam. Curabitur sapien odio, interdum quis erat facilisis,
          convallis congue tellus. Nulla dui sem, mattis eu ornare quis,
          tristique a leo. Vivamus ac magna turpis. Vivamus tempus, metus ut
          aliquam rhoncus, leo sem lobortis arcu, sed lobortis justo mi varius
          turpis. Integer volutpat metus non elit tincidunt accumsan sed a erat.
          Proin dapibus, lectus et tempor laoreet, odio massa ullamcorper velit,
          nec luctus nisi sem at sem. Aliquam erat volutpat. Quisque egestas
          interdum urna. Vivamus ac lacus felis. Donec justo ex, convallis sit
          amet justo et, condimentum lacinia dolor. Fusce consequat iaculis
          metus eu iaculis. Duis consectetur leo id tortor luctus, quis lobortis
          lectus varius. Orci varius natoque penatibus et magnis dis parturient
          montes, nascetur ridiculus mus. Praesent vitae consectetur mi.
        </Dialog.Content>
        <Dialog.Footer sticky>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="primary">Primary Action</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

## Usage Guidelines

| Standard | Requirement |
| - | - |
| DoFocus on important tasks | Use Dialogs to bring focus to important tasks. Situations that require user action before continuing, confirmations, and quick, infrequent tasks are among the most common use cases. |
| DoConfirm important actions | Use Dialogs to confirm important, higher-risk actions. Destructive, hard to reverse decisions particularly benefit from Dialog usage. Not all actions require a confirmation; use only as needed. |
| DoMulti-step footer navigation | For Dialogs with multiple steps, use the recommended footer arrangement for navigation, adjusting the final step's primary action copy as needed; see [Multi-step Dialogs](#multi-step-dialogs). |
| DoForms in Dialogs | Keep small Dialog forms short, using multi-step when needed; fullscreen Dialogs may use forms as complex as an ordinary page; see [Forms in Dialogs](#forms-in-dialogs). |
| DoStack a Dialog with a fullscreen Dialog | Since a fullscreen Dialog mimics a standard page, stack a smaller Dialog as needed; see [Stacking a Dialog with a fullscreen Dialog](#stacking-a-dialog-with-a-fullscreen-dialog). |
| Don'tOveruse Dialogs | Dialogs are interruptive UIs by design and may disorient users when used mid-flow. It is easy to overuse Dialogs in flows, particularly in complex UIs — reduce usage to only what is needed and find alternatives to surface information on the page. Overuse restricts a user's ability to perform other tasks, blocks other content on the screen, can be difficult to find and relocate, is difficult to directly link to, and adds additional interaction steps to open and close. |
| Don'tCommunicate errors | Using a Dialog to convey errors is an anti-pattern. It requires a user to actively remember its contents once the Dialog is closed. See the [error pattern](https://anvil.servicetitan.com/docs/patterns/errors-and-warnings/web.md) for alternatives. |
| Don'tStacking smaller Dialogs | Avoid stacking smaller Dialogs together; see [Avoid stacking smaller Dialogs together](#avoid-stacking-smaller-dialogs-together). |

### Alternatives

#### Dialog vs Alert

Dialogs are interruptive by design, while an Alert is non-disruptive. Prefer using an Alert when information needs to be conveyed, only using a Dialog when there is a clear need to interrupt a flow, such as a confirmation of destructive or irreversible actions.

#### Dialog vs Drawer

| | Dialog | Drawer |
| - | - | - |
| Complete a secondary task | Simple, one step tasks | 1-3 steps at most. Tasks with more steps should be on a separate page |
| Display additional information related to the context in the main page | Yes, ideal for short content | Yes, ideal for longer, more complex information |
| Confirm user actions | Yes | No |

#### Dialog vs Popover

Both Dialogs and Popovers overlay elements on the page. Dialogs are preferable when a situation requires user attention and for more complicated flows.

| | Popover | Dialog |
| - | - | - |
| Content allowed in the container? | Any content | Any content |
| Complexity of content? | From simple to a few interactions | From simple to high complex |
| Backdrop applied to page? | No | Yes |

### How to Use

#### Multi-step Dialogs

Dialogs of all sizes may involve multiple steps. We recommend using the footer arrangement shown for user navigation. Adjust the final step's primary action copy as needed.

dialog-multistep-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-multistep-do?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "35rem", minHeight: "12rem" }}>
      <Dialog enableScrollChaining open size="large">
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>Body text in the Dialog.</Dialog.Content>
        <Dialog.Footer>
          <Button>Back</Button>
          {/* spacer */}
          <Flex grow="1" />
          <Dialog.CancelButton>Cancel</Dialog.CancelButton>
          <Button appearance="primary">Next</Button>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

dialog-multistep-final-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-multistep-final-do?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "35rem", minHeight: "12rem" }}>
      <Dialog enableScrollChaining open size="large">
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>Body text in the Dialog.</Dialog.Content>
        <Dialog.Footer>
          <Button>Back</Button>
          {/* spacer */}
          <Flex grow="1" />
          <Dialog.CancelButton>Cancel</Dialog.CancelButton>
          <Button appearance="primary">Done</Button>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

#### Forms in Dialogs

A form can exist within a Dialog. Beyond the [standard usage guidance to Forms](https://anvil.servicetitan.com/docs/patterns/forms/web.md), small Dialogs should especially keep form length low, and make use of multi-steps when needed. Fullscreen Dialogs may use forms as complex as an ordinary page.

dialog-form-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-form-do?file=src/App.tsx)

```tsx lines theme={null}
import {
  Dialog,
  Grid,
  TextField,
  Radio,
  Flex,
  Button,
} from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "23rem", minHeight: "22rem" }}>
      <Dialog enableScrollChaining open>
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>
          <Grid gap="6">
            <TextField label="Email Address" />

            <Radio.Group legend="Permissions">
              <Radio name="role" value="edit" label="Can view" checked />
              <Radio name="role" value="view" label="Can edit" />
            </Radio.Group>
          </Grid>
        </Dialog.Content>
        <Dialog.Footer>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="primary">Share</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

#### Avoid stacking smaller Dialogs together

dialog-nested-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-nested-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "35rem", minHeight: "19rem" }}>
      <Dialog enableScrollChaining open size="large">
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
          <br />
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
        </Dialog.Content>
        <Dialog.Footer>
          <Button>Back</Button>
          {/* spacer */}
          <Flex grow="1" />
          <Dialog.CancelButton>Cancel</Dialog.CancelButton>
          <Button appearance="primary">Next</Button>
        </Dialog.Footer>
      </Dialog>
      <Dialog enableScrollChaining open>
        <Dialog.Header>Discard unsaved changes?</Dialog.Header>
        <Dialog.Content>This cannot be undone.</Dialog.Content>
        <Dialog.Footer sticky>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="danger">Discard Changes</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

#### Stacking a Dialog with a fullscreen Dialog

Since a fullscreen Dialog mimics a standard page, stack a smaller Dialog as needed.

dialog-nested-fullscreen-do-full-page

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-nested-fullscreen-do-full-page?file=src/App.tsx)

```tsx lines theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div>
      <Dialog enableScrollChaining open fullScreen>
        <Dialog.Header>Header text</Dialog.Header>
        <Dialog.Content>
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
          <br />
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
          <br />
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
          <br />
          Body text in the Dialog. Lorem ipsum dolor sit amet, consectetur
          adipiscing elit. Nunc nibh sem, ornare sit amet ultrices ac, malesuada
          ac nisi.
        </Dialog.Content>
        <Dialog.Footer>
          <Button>Back</Button>
          {/* spacer */}
          <Flex grow="1" />
          <Dialog.CancelButton>Cancel</Dialog.CancelButton>
          <Button appearance="primary">Next</Button>
        </Dialog.Footer>
      </Dialog>
      <Dialog enableScrollChaining open>
        <Dialog.Header>Discard unsaved changes?</Dialog.Header>
        <Dialog.Content>This cannot be undone.</Dialog.Content>
        <Dialog.Footer sticky>
          <Flex justifyContent="flex-end">
            <Flex gap="3">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="danger">Discard Changes</Button>
            </Flex>
          </Flex>
        </Dialog.Footer>
      </Dialog>
    </div>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

## Content

Content within the Dialog should be concise, actionable, and clearly communicate the task or information.

### Write titles as verbs

Titles should have a clear verb + noun question or statement.

dialog-header-verb-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-header-verb-do?file=src/App.tsx)

```tsx lines theme={null}
import { Text, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid gap="6" placeContent="center">
      <Text variant="headline" el="h1">
        Edit customer information
      </Text>
      <Text variant="headline" el="h1">
        Delete message?
      </Text>
      <Text variant="headline" el="h1">
        Discard unsaved changes?
      </Text>
    </Grid>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

dialog-header-verb-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-header-verb-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Text, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid gap="6" placeContent="center">
      <Text variant="headline" el="h1">
        Edit the service agreement for this customer
      </Text>
      <Text variant="headline" el="h1">
        Are you sure you want to remove the invoice?
      </Text>
      <Text variant="headline" el="h1">
        Delete?
      </Text>
    </Grid>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

### Make body content concise and actionable

Use imperative verbs when telling users what they need to know or do. Don't use permissive language like "you can".

dialog-content-concise-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-content-concise-do?file=src/App.tsx)

```tsx lines theme={null}
import { Text, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid gap="6" placeContent="center">
      <Text>Notification emails will be sent to this address.</Text>
      <Text>This cannot be undone.</Text>
    </Grid>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

dialog-content-concise-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-content-concise-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Text, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid gap="6" placeContent="center">
      <Text>You can edit the email address where emails will be sent.</Text>
      <Text>
        Are you sure you want to delete this job? You can’t reverse this.
      </Text>
    </Grid>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

### Buttons should be clear, predictable, and action-oriented

Users should be able to predict what will happen when they click a button.

Lead with a strong verb that encourages action. Use a verb/noun pair on actions in most cases. Common actions like Save, Close, or Cancel do not require an accompanying noun.

dialog-footer-action-oriented-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-footer-action-oriented-do?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid gap="6" placeContent="center">
      <Button appearance="primary">Create Order</Button>
      <Button appearance="primary">Print Label</Button>
    </Grid>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

dialog-footer-action-oriented-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-footer-action-oriented-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid gap="6" placeContent="center">
      <Button appearance="primary">New Invoice</Button>
      <Button appearance="primary">Schedule</Button>
    </Grid>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

## Keyboard Interaction

Users can navigate the Dialog using standard keyboard controls.

| Key | Interaction |
| - | - |
| Tab | Cycles through tab-able elements within the Dialog |
| Escape | Closes the Dialog |

### Accessibility

#### Focus management

There are a few different things to keep in mind with the Dialog's focus / keyboard behavior.

##### Trap focus within the Dialog

By default, the Dialog will trap focus within itself. Elements outside of the Dialog would not be accessible by keyboard, unless another element itself took the focus.

##### First focus management

What element the Dialog focuses on open is dependent on a few factors:

* If the body content has a focus-able element, the Dialog will focus on the first of these elements.
* If the body does not have have an element, then the following is the preferred first-focus element:
  * With no actions at all, focus first on the close action.
  * When a primary action exists, focus first on the primary action.
  * When a primary destructive action exists, focus first on the secondary cancel action.

##### Return focus on close

When a Dialog is closed and the original context that triggered the Dialog is still present, focus should return to the element that triggered the Dialog.

For more guidance on focus management with dynamic content, see [changing content best practices](https://anvil.servicetitan.com/docs/accessibility/changing-content.md).

## Code

**Implementation**

dialog-playground

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-playground?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Dialog, Flex, Button } from "@servicetitan/anvil2";
import { useState } from "react";

function App() {
  const [isOpen, setIsOpen] = useState(true);

  return (
    <div style={{ minHeight: "254.5px" }}>
      <Flex justifyContent="center" style={{ paddingBlock: "var(--size-12)" }}>
        <Button onClick={() => setIsOpen(true)}>Open Dialog</Button>
        <Dialog open={isOpen} onClose={() => setIsOpen(false)}>
          <Dialog.Header>Dialog Header</Dialog.Header>
          <Dialog.Content>This is the dialog body content.</Dialog.Content>
          <Dialog.Footer>
            <Flex gap="3" justifyContent="flex-end">
              <Dialog.CancelButton>Cancel</Dialog.CancelButton>
              <Button appearance="primary">Continue</Button>
            </Flex>
          </Dialog.Footer>
        </Dialog>
      </Flex>
    </div>
  );
}

export default App;
```

## Common Examples

```tsx theme={null}
import { Button, Dialog, Flex } from "@servicetitan/anvil2";
import { useState } from "react";

function ExampleComponent() {
  const [isOpen, setIsOpen] = useState(true);
  return (
    <Dialog open={isOpen} onClose={() => setIsOpen(false)}>
      <Dialog.Header>Dialog Header</Dialog.Header>
      <Dialog.Content>This is the dialog body content.</Dialog.Content>
      <Dialog.Footer>
        <Flex gap="3" justifyContent="flex-end">
          <Dialog.CancelButton>Cancel</Dialog.CancelButton>
          <Button appearance="primary">Continue</Button>
        </Flex>
      </Dialog.Footer>
    </Dialog>
  );
}
```

### Opening dialogs

Dialogs should be shown and hidden using the `open` prop, rather than conditionally rendering the entire component.

This allows us to follow the HTML standard for `dialog` elements, which is what we render under-the-hood. This also prevents some other issues, such as skipping the exit animation or missing toast messages.

```tsx theme={null}
<Dialog open={shouldBeOpen} {...otherProps} />
```

> [!TIP]
>
> **Do**

```tsx theme={null}
{
  shouldBeOpen && <Dialog {...otherProps} />;
}
```

> [!CAUTION]
>
> **Don't**

### Rendering content on open

By default, dialog content is rendered on page load, but hidden until the dialog is opened. In some cases, such as making heavy API calls or with content that may require large re-renders based on other user actions, it could be advantageous to wait to render the dialog content.

To render dialog content only when it is opened, conditionally render the `children` based on the state used in the `Dialog.open` prop.

```tsx theme={null}
import { Dialog, Flex, TextField } from "@servicetitan/anvil2";
import { useState } from "react";

function ExampleComponent() {
  const [isOpen, setIsOpen] = useState(true);
  return (
    <Dialog open={isOpen} onClose={() => setIsOpen(false)}>
      {isOpen && (
        <Dialog.Header>Dialog Header</Dialog.Header>
        <Dialog.Content>
          <ComponentWithHeavyAPICalls />
        </Dialog.Content>
        <Dialog.Footer>
          <Flex gap="3" justifyContent="flex-end">
            <Dialog.CancelButton>
              Cancel
            </Dialog.CancelButton>
          </Flex>
        </Dialog.Footer>
      )}
    </Dialog>
  )
}
```

### Sticky Content

Use the `sticky` prop on `Dialog.Content` to keep important UI elements (like search fields or filters) visible while other content scrolls. This is useful for dialogs with long, scrollable content.

```tsx theme={null}
import { Button, Dialog, Flex, TextField, Text } from "@servicetitan/anvil2";
import { useState } from "react";

function ExampleComponent() {
  const [isOpen, setIsOpen] = useState(true);
  return (
    <Dialog open={isOpen} onClose={() => setIsOpen(false)}>
      <Dialog.Header>Filter Items</Dialog.Header>
      <Dialog.Content sticky>
        <TextField placeholder="Search items..." />
      </Dialog.Content>
      <Dialog.Content>
        <Flex direction="column" gap="2">
          {Array.from({ length: 50 }, (_, i) => (
            <Text key={i}>Item {i + 1}</Text>
          ))}
        </Flex>
      </Dialog.Content>
      <Dialog.Footer sticky>
        <Flex gap="3" justifyContent="flex-end">
          <Dialog.CancelButton>Cancel</Dialog.CancelButton>
          <Button appearance="primary">Apply</Button>
        </Flex>
      </Dialog.Footer>
    </Dialog>
  );
}
```

The search field will remain visible at the top while the list of items scrolls below it.

### Choosing initial focus

By default, the `Dialog` focuses the first focusable element that is not the `Dialog.CancelButton`. To override this behavior, use the `initialFocusResolver` prop to specify which element receives focus when the dialog opens.

The `initialFocusResolver` function receives an array of all focusable elements within the dialog and returns the one that should receive initial focus.

[dialog-initial-focus](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/dialog-initial-focus?embed=1\&hideNavigation=1\&hideExplorer=1\&terminalHeight=0\&file=src/App.tsx)

### Closing dialogs

Clicking a `Dialog.CancelButton` or the close button inside of `Dialog.Header` will trigger the `Dialog.onClose` to run, where you can add a callback to control the open/close state of the dialog.

```tsx theme={null}
<Dialog open={isOpen} onClose={() => setIsOpen(false)}>
  {/* The header includes a close button that will trigger the Dialog.onClose */}
  <Dialog.Header>Header</Dialog.Header>
  <Dialog.Content>Content</Dialog.Content>
  <Dialog.Footer>
    {/* The cancel button will also trigger the Dialog.onClose */}
    <Dialog.CancelButton>Cancel</Dialog.CancelButton>
  </Dialog.Footer>
</Dialog>
```

#### Closing callbacks

In addition to `onClose`–which indicates a user has chosen to close a dialog–the component also offers two animation callbacks `onCloseAnimationStart` and `onCloseAnimationComplete`.

You may use these callbacks to perform additional actions related to the presentation of the `Dialog`. For example, if you have a dialog containing a form, you may wish to reset the form state after the close animation has played so that the user doesn't see an empty form briefly when closing the dialog.

```tsx theme={null}
const [open, setOpen] = useState(false);
const [field, setField] = useState("");

const handleSubmit = () => {
  setOpen(false);
};
const handleDialogAnimationComplete = () => {
  setField("");
};

return (
  <>
    <Button onClick={() => setOpen(true)}>Open</Button>
    <Dialog
      open={open}
      onClose={() => setOpen(false)}
      onCloseAnimationComplete={handleDialogAnimationComplete}
    >
      <Dialog.Header>What is your favorite color?</Dialog.Header>
      <Dialog.Content>
        <TextField
          label="Favorite color"
          value={field}
          onChange={(e) => setField(e.target.value)}
          style={{ width: "100%" }}
          autoComplete="off"
        />
      </Dialog.Content>
      <Dialog.Footer>
        <Button onClick={() => setOpen(false)}>Cancel</Button>
        <Button appearance="primary" onClick={handleSubmit}>
          Submit
        </Button>
      </Dialog.Footer>
    </Dialog>
  </>
);
```

### Dialogs and Toasts

Due to the way the HTML `dialog` element renders in the browser's top layer, the `Dialog` component includes an internal `Toaster` for rendering toast messages. This should be unnoticeable to implementors and users, but there may be edge cases the result in toasts not rendering as expected.

Please reach out to us in the [#ask-designsystem](https://servicetitan.enterprise.slack.com/archives/CBSRGHTRS) channel on Slack if any edge cases related to dialogs and toasts are found!

## Anti-Patterns

### Conditional rendering

The openness should be controlled by the `open` prop and not by conditional rendering -- this is an anti-pattern for Dialog.

```tsx theme={null}
❌
{condition && <Dialog open>...</Dialog>}

✅
<Dialog open={condition}>...</Dialog>
```

#### Resetting content

HTML Dialog show/hide content but it doesn't remove from the DOM which means the reset doesn't happen automatically. To do the reset, use `key` on `<Dialog.Content>` . Since `<Dialog.Content>` is always present in DOM, you can add conditional to, or in, the `<Dialog.Content>` as well.

#### More details

Dialog uses HTML Dialog which is powered by HTML top-layers. This puts them on top of EVERYTHING regardless z-index and we use the HTML top-layer to avoid stacking context and z-index issues, ensuring Dialog always appear above all content without needing manual z-index adjustments. Dialog internally has custom mechanism to ensure Toasts to be on top for ServiceTitan app and requires Dialog to be present on page load.

**Dialog Props**

```tsx theme={null}
<Dialog
  open={true}
  onClose={() => setIsOpen(false)}
  disableCloseOnClickOutside={false}
  disableCloseOnEscape={false}
  fullScreen={false}
  size="medium"
  onCloseAnimationComplete={() => {}}
  onCloseAnimationStart={() => {}}
  onOpenAnimationComplete={() => {}}
  onOpenAnimationStart={() => {}}
>
  <Dialog.Header>Header</Dialog.Header>
  <Dialog.Content>Content</Dialog.Content>
  <Dialog.Footer>
    <Dialog.CancelButton>Cancel</Dialog.CancelButton>
  </Dialog.Footer>
</Dialog>
```

## `Dialog` Props

In addition to the props listed below, the `Dialog` component can accept any valid HTML `dialog` props.

**defaultDrillDownIndex** `number` (default: 0)

Screen the first `next()` opens from a closed [DrillDown](https://anvil.servicetitan.com/docs/components/drilldown/web.md) stack. The dialog starts with no active screen. Closing the dialog restores this index.

**disableCloseOnClickOutside** `boolean` (default: false)

When `true`, clicking outside the dialog will not close it.

**disableCloseOnEscape** `boolean` (default: false)

When `true`, pressing the escape key will not close the dialog.

**enableScrollChaining** `boolean`

Enables scroll chaining behavior.

**fullScreen** `boolean` (default: false)

**initialFocusResolver** `(focusables: FocusableElement[]) =\> FocusableElement`

Given an array of focusable elements, returns the element that should receive initial focus.

**onClickOutside** `(e: MouseEvent) =\> void`

Callback when clicking outside the dialog.

**onClose** `() =\> void`

Indicates the user has closed the dialog. Use this to update your state.

**onCloseAnimationComplete** `() =\> void`

Callback indicating the dialog has animated out. Use this to clean up views as
needed.

**onCloseAnimationStart** `() =\> void`

Callback indicating the dialog is beginning to animate out.

**onOpenAnimationComplete** `() =\> void`

Callback indicating the dialog has animated in.

**onOpenAnimationStart** `() =\> void`

Callback indicating the dialog is beginning to animate in.

**open** `boolean` (default: false)

**size** `"medium" | "large" | "xlarge"` (default: medium)

**Dialog.Header Props**

```tsx theme={null}
<Dialog.Header>Dialog Header</Dialog.Header>
```

## `Dialog.Header` Props

The `Dialog.Header` component can accept any valid HTML header props.

**Dialog.Content Props**

```tsx theme={null}
<Dialog.Content sticky>Dialog content</Dialog.Content>
```

## `Dialog.Content` Props

In addition to the props listed below, the `Dialog.Content` component can accept any valid HTML div props.

**sticky** `boolean` (default: false)

When `true`, the content will stick below the header during scroll.

**Dialog.Footer Props**

```tsx theme={null}
<Dialog.Footer sticky={false}>
  <Button>Action</Button>
</Dialog.Footer>
```

## `Dialog.Footer` Props

**sticky** `boolean` (default: false)

**Dialog.CancelButton Props**

```tsx theme={null}
<Dialog.CancelButton appearance="secondary" onClick={() => {}}>
  Cancel
</Dialog.CancelButton>
```

## `Dialog.CancelButton` Props

The `Dialog.CancelButton` component can accept the same props as the `Button` component.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.