# Source: https://tamagui.dev/ui/dialog.md

---
title: Dialog
description: Show a modal with configurable layout and accessible actions.
name: dialog
component: Dialog
package: dialog
demoName: Dialog
---

<HeroContainer showAnimationDriverControl>
  <DialogDemo />
</HeroContainer>

```tsx hero template=Dialog

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible with dev-time checks to ensure ARIA props.`,
  ]}
/>

Dialog is a great way to show content inside a new floating window above
content. Be sure to open the code example above for a copy-paste implementation.

## Installation

Dialog is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/dialog
```

If you aren't using `tamagui` and instead using the `@tamagui/dialog` package
separately, you'll first need to install the `@tamagui/portal` package:

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

## Anatomy

```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'

export default () => (
  <Dialog>
    <Dialog.Trigger />
    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* Optional: Control focus behavior */}
    <Dialog.FocusScope loop trapped focusOnIdle={true}>
      <Dialog.FocusScope.Scope>
        {/* Focus scope will be applied to children */}
      </Dialog.FocusScope.Scope>
    </Dialog.FocusScope>
  </Dialog>
)
```

## Scoping

Dialog supports scoping which lets you mount one or more Dialog instances at the
root of your app, while having a deeply nested child Trigger or Content attach
to the proper parent Dialog instance.

In performance sensitive areas you may want to take advantage of this, it allows
you to only need to render the Dialog.Trigger inside the sensitive area as
Dialogs aren't the cheapest component - they have a lot of functionality.

Here's the basic anatomy of using `scope` and placing your Dialog higher up for
performance:

```tsx fileName=_layout.tsx
import { Dialog } from 'tamagui'

// in your root layout:
export default ({ children }) => (
  <Dialog scope="user-profile">
    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* the rest of your app, note that it's inside of Dialog */}
    {children}
  </Dialog>
)
```

```tsx fileName=UserProfile.tsx
export default () => (
  <Dialog.Trigger scope="user-profile">
    <Button>Open Profile</Button>
  </Dialog.Trigger>
)
```

Note that the `Trigger` scope ties to the `Dialog` scope.

## Dismissal Behavior

By default, dialogs can be dismissed by:
- Clicking outside the dialog content (on the overlay)
- Pressing the Escape key
- Clicking a Dialog.Close element

### Modal vs Non-Modal Dialogs

- **Modal dialogs** (`modal={true}`, which is the default):
  - In v1, have `disableOutsidePointerEvents` set to `true` by default
  - Still dismiss on outside click, but prevent interaction with elements behind the dialog
  - Prevent right-click dismissal (right-clicks on the overlay are ignored)
  
- **Non-modal dialogs** (`modal={false}`):
  - Allow interaction with elements behind the dialog
  - Dismiss on any outside click
  - Do not trap focus

### Preventing Outside Dismissal

To prevent a dialog from closing when clicking outside:

```tsx
<Dialog.Content
  onPointerDownOutside={(event) => {
    event.preventDefault()
  }}
>
  {/* Dialog contents */}
</Dialog.Content>
```

## API Reference

### Dialog

Contains every component for the dialog. Beyond
[Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Dialog.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      description: `Renders into root of app instead of inline`,
    },
    {
      name: 'disableRemoveScroll',
      type: 'boolean',
      required: false,
      description: `Used to disable the automatic removal of scrolling from the page when open.`,
    },
  ]}
/>

### Dialog.Trigger

Just [Tamagui Props](/docs/intro/props).

### Dialog.Portal

Renders Dialog into appropriate container. Beyond
[Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Dialog.Content

Main container for Dialog content, this is where you should apply animations.

Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
    {
      name: 'disableOutsidePointerEvents',
      type: 'boolean',
      required: false,
      description: `When true, hover/focus/click interactions will be disabled on elements outside the Dialog. Users will need to click twice on outside elements to interact with them: once to close the Dialog, and again to trigger the element. Note: In v1, modal dialogs have this set to true by default.`,
    },
  ]}
/>

### Dialog.Overlay

Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### Dialog.Title

Required. Can wrap in VisuallyHidden to hide.

Defaults to H2, see [Headings](/docs/components/headings).

### Dialog.Description

Required. Can wrap in VisuallyHidden to hide.

Defaults to Paragraph, see [Paragraph](/docs/components/text).

### Dialog.Close

Closes the Dialog, accepts the same props as YStack. Recommended to use with
your own component and `asChild`.

<PropsTable
  data={[
    {
      name: 'displayWhenAdapted',
      type: 'boolean',
      description: `By default Close elements hide when Adapt is active. If set to true, they will show when adapted.`,
    },
  ]}
/>

Just [Tamagui Props](/docs/intro/props).

### Dialog.FocusScope

Provides access to the underlying FocusScope component used by Dialog for focus
management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Dialog.Sheet

When used with `Adapt`, Dialog will render as a sheet when that breakpoint is
active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Dialog.Sheet.Frame` to insert the contents
given to `Dialog.Content`

```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'

export default () => (
  <Dialog>
    <Dialog.Trigger />

    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* optionally change to sheet when small screen */}
    <Dialog.Adapt when="maxMd">
      <Dialog.Sheet>
        <Dialog.Sheet.Frame>
          <Dialog.Adapt.Contents />
        </Dialog.Sheet.Frame>
        <Dialog.Sheet.Overlay />
      </Dialog.Sheet>
    </Dialog.Adapt>
  </Dialog>
)
```

<Notice>
  Note that Dialog.Sheet currently doesn't preserve state of the contents when
  it transitions between Sheet and Portal. In the future, we can do this on the
  web using react-reparenting.
</Notice>

### PortalProvider

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Examples

### Inside native modals

If you're using native modals (maybe from react-navigation), you'll notice the
Dialogs won't show up inside the modal. To get around this, you should wrap your
screen inside `PortalProvider`, like so:

```tsx
import { PortalProvider } from 'tamagui'

// this component used in react-navigation/expo-router with `presentation: "modal"`
export function Page() {
  return (
    <PortalProvider>
      {/* rest of your page, including the Dialog... */}
    </PortalProvider>
  )
}
```