> ## 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 – Code

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

export const LiveCode = ({children, customHeight, clickToLoad, example, fullWidth, fullHeight, hideCodeInLiveCode, screenshot, screenshotOnly, showCode: showCodeProp}) => {
  const SCREENSHOTS_BASE = "https://servicetitan.github.io/anvil2-docs-live-code/screenshots";
  const STACKBLITZ_BASE = "https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples";
  const [showCodeBlock, setShowCodeBlock] = useState(showCodeProp ?? false);
  const [isLocalOverride, setIsLocalOverride] = useState(false);
  useEffect(() => {
    const examplePath = `/images/live-code-screenshots-tmp/${example}.png`;
    fetch(examplePath, {
      method: "HEAD"
    }).then(r => {
      if (r.ok) setIsLocalOverride(true);
    }).catch(() => {});
  }, [example]);
  const screenshotBase = isLocalOverride ? "/images/live-code-screenshots-tmp" : SCREENSHOTS_BASE;
  if (screenshotOnly) {
    return <Frame className="flex flex-col">
        <div className="flex dark:hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#FFFFFF"
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>
        <div className="hidden dark:flex" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#141414"
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>
      </Frame>;
  }
  if (screenshot) {
    return <Frame className="flex flex-col -mb-2">
        <div className="flex dark:hidden bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>

        <div className="hidden dark:flex bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      background: "#141414",
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>

        <div className="flex justify-end items-center text-xs py-2 px-1 gap-4">
          {!showCodeProp ? <button className="inline-flex justify-end items-center text-gray-700 dark:text-gray-50 hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1 cursor-pointer" onClick={() => setShowCodeBlock(!showCodeBlock)} style={{
      appearance: "none"
    }}>
              <Icon icon="code" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
              <span>{showCodeBlock ? "Hide code" : "Show code"}</span>
            </button> : null}

          <a className="inline-flex justify-end items-center hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1" href={`${STACKBLITZ_BASE}/${example}?file=src/App.tsx`} target="_blank" rel="noreferrer">
            <Icon icon="bolt" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
            <span>StackBlitz demo</span>
          </a>
        </div>

        <div className="grid transition-[grid-template-rows] duration-300 ease-in-out overflow-auto overflow-y-hidden overflow-x-auto" style={showCodeBlock ? {
      gridTemplateRows: "1fr"
    } : {
      gridTemplateRows: "0fr"
    }}>
          <div style={{
      minHeight: 0,
      overflowX: "auto",
      overflowY: "hidden",
      marginBlockStart: "-1.25rem",
      marginBlockEnd: "-1.5rem"
    }}>
            {children}
          </div>
        </div>
      </Frame>;
  } else {
    return <div style={{
      display: "flex",
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px",
      resize: "vertical",
      overflow: "auto"
    }}>
        <iframe title={example} style={{
      flex: 1,
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px"
    }} src={`${STACKBLITZ_BASE}/${example}?embed=1&hideNavigation=1&hideExplorer=1&terminalHeight=0&file=src/App.tsx${clickToLoad ? "&ctl=1" : ""}${hideCodeInLiveCode ? "&view=preview" : ""}`} allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />
      </div>;
  }
};

<Tabs>
  <Tab title="Implementation">
    <LiveCode showCode example="dialog-playground" fullWidth screenshot>
      ```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;
      ```
    </LiveCode>

    ## 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} />
    ```

    <Check>**Do**</Check>

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

    <Danger>**Don't**</Danger>

    ### 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.

    <LiveCode showCode example="dialog-initial-focus" fullWidth>
      ```tsx lines expandable theme={null}
      import { Dialog, Flex, Button, TextField } from "@servicetitan/anvil2";
      import { useState } from "react";

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

        return (
          <div style={{ minHeight: "320px" }}>
            <Flex justifyContent="center" style={{ paddingBlock: "var(--size-12)" }}>
              <Button onClick={() => setIsOpen(true)}>Open Dialog</Button>
              <Dialog
                open={isOpen}
                onClose={() => setIsOpen(false)}
                initialFocusResolver={(focusables) =>
                  focusables.find(
                    // Focus the save button when the dialog opens
                    (el) =>
                      el.textContent === "Save" && el instanceof HTMLButtonElement,
                  ) || focusables[0]
                }
              >
                <Dialog.Header>Add New Contact</Dialog.Header>
                <Dialog.Content>
                  <Flex direction="column" gap="4">
                    <TextField
                      label="Name"
                      name="name"
                      autoComplete="off"
                      value="John"
                    />
                    <TextField
                      label="Email"
                      name="email"
                      autoComplete="off"
                      value="john@example.com"
                    />
                    <TextField
                      label="Phone"
                      name="phone"
                      autoComplete="off"
                      value="123-456-7890"
                    />
                  </Flex>
                </Dialog.Content>
                <Dialog.Footer>
                  <Flex gap="3" justifyContent="flex-end">
                    <Dialog.CancelButton>Cancel</Dialog.CancelButton>
                    <Button appearance="primary">Save</Button>
                  </Flex>
                </Dialog.Footer>
              </Dialog>
            </Flex>
          </div>
        );
      }

      export default App;
      ```
    </LiveCode>

    ### 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.
  </Tab>

  <Tab title="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.

    <ParamField path="disableCloseOnClickOutside" type="boolean" default="false">
      When `true`, clicking outside the dialog will not close it.
    </ParamField>

    <ParamField path="disableCloseOnEscape" type="boolean" default="false">
      When `true`, pressing the escape key will not close the dialog.
    </ParamField>

    <ParamField path="enableScrollChaining" type="boolean">
      Enables scroll chaining behavior.
    </ParamField>

    <ParamField path="fullScreen" type="boolean" default="false" />

    <ParamField path="initialFocusResolver" type="(focusables: FocusableElement[]) => FocusableElement">
      Given an array of focusable elements, returns the element that should receive initial focus.
    </ParamField>

    <ParamField path="onClickOutside" type="(e: MouseEvent) => void">
      Callback when clicking outside the dialog.
    </ParamField>

    <ParamField path="onClose" type="() => void">
      Indicates the user has closed the dialog. Use this to update your state.
    </ParamField>

    <ParamField path="onCloseAnimationComplete" type="() => void">
      Callback indicating the dialog has animated out. Use this to clean up views as
      needed.
    </ParamField>

    <ParamField path="onCloseAnimationStart" type="() => void">
      Callback indicating the dialog is beginning to animate out.
    </ParamField>

    <ParamField path="onOpenAnimationComplete" type="() => void">
      Callback indicating the dialog has animated in.
    </ParamField>

    <ParamField path="onOpenAnimationStart" type="() => void">
      Callback indicating the dialog is beginning to animate in.
    </ParamField>

    <ParamField path="open" type="boolean" default="false" />

    <ParamField path="size" type={`"medium" | "large" | "xlarge"`} default="medium" />
  </Tab>

  <Tab title="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.
  </Tab>

  <Tab title="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.

    <ParamField path="sticky" type="boolean" default="false">
      When `true`, the content will stick below the header during scroll.
    </ParamField>
  </Tab>

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

    ## `Dialog.Footer` Props

    <ParamField path="sticky" type="boolean" default="false" />
  </Tab>

  <Tab title="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.
  </Tab>
</Tabs>
