Dialog
Show a modal with configurable layout and accessible actions.
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:
yarn add @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:
yarn add @tamagui/portal
Then add PortalProvider
to the root of your app:
App.tsx
import { PortalProvider } from '@tamagui/portal'import YourApp from './components/YourApp'function App() {return (<PortalProvider shouldAddRootHost><YourApp /></PortalProvider>)}export default App
Anatomy
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 has a lot of functionality.
Here's the basic anatomy of using scope
and placing your Dialog higher up for
performance:
_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 of your app, note that it's inside of Dialog */}{children}</Dialog>)
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.
API Reference
Dialog
Contains every component for the dialog. Beyond Tamagui Props, adds:
Props
children (required)
React.ReactNode
Must contain Dialog.Content
size
SizeTokens
Passes size down too all sub-components when set for padding, arrow, borderRadius
open
boolean
defaultOpen
boolean
onOpenChange
(open: boolean) => void
modal
boolean
Default:
true
Renders into root of app instead of inline
disableRemoveScroll
boolean
Used to disable the automatic removal of scrolling from the page when open.
Dialog.Trigger
Just Tamagui Props.
Dialog.Portal
Renders Dialog into appropriate container. Beyond Tamagui Props, adds:
Props
forceMount
boolean
Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.
unstyled
boolean
Removes all default Tamagui styles.
Dialog.Content
Main container for Dialog content, this is where you should apply animations.
Beyond Tamagui Props, adds:
Props
forceMount
boolean
Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.
unstyled
boolean
Removes all default Tamagui styles.
Dialog.Overlay
Displays behind Content. Beyond Tamagui Props, adds:
Props
forceMount
boolean
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.
Dialog.Description
Required. Can wrap in VisuallyHidden to hide.
Defaults to Paragraph, see Paragraph.
Dialog.Close
Closes the Dialog, accepts the same props as YStack. Recommended to use with
your own component and asChild
.
Props
displayWhenAdapted
boolean
By default Close elements hide when Adapt is active. If set to true, they will show when adapted.
Just Tamagui 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.
Props
enabled
boolean
Default:
true
Whether focus management is enabled
loop
boolean
Default:
false
When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable
trapped
boolean
Default:
false
When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus
focusOnIdle
boolean | number
Default:
false
When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations
onMountAutoFocus
(event: Event) => void
Event handler called when auto-focusing on mount. Can be prevented
onUnmountAutoFocus
(event: Event) => void
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 for more props.
Must use Adapt.Contents
inside the Dialog.Sheet.Frame
to insert the contents
given to Dialog.Content
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>)
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.
PortalProvider
Props
shouldAddRootHost
boolean
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:
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>)}