Dialog
Show a modal with configurable layout and accessible actions
import { X } from '@tamagui/lucide-icons'import {Adapt,Button,Dialog,Fieldset,Input,Label,Paragraph,Sheet,TooltipSimple,Unspaced,XStack,} from 'tamagui'import { SelectDemoItem } from './SelectDemo'export function DialogDemo() {return <DialogInstance />}function DialogInstance() {return (<Dialog modal><Dialog.Trigger asChild><Button>Show Dialog</Button></Dialog.Trigger><Adapt when="sm" platform="touch"><Sheet animation="medium" zIndex={200000} modal dismissOnSnapToBottom><Sheet.Frame padding="$4" gap="$4"><Adapt.Contents /></Sheet.Frame><Sheet.Overlay animation="lazy" enterStyle={{ opacity: 0 }} exitStyle={{ opacity: 0 }} /></Sheet></Adapt><Dialog.Portal><Dialog.Overlay key="overlay" animation="slow" opacity={0.5} enterStyle={{ opacity: 0 }} exitStyle={{ opacity: 0 }} /><Dialog.Content bordered elevate key="content" animateOnly={['transform', 'opacity']} animation={[ 'quicker', { opacity: { overshootClamping: true, }, }, ]} enterStyle={{ x: 0, y: -20, opacity: 0, scale: 0.9 }} exitStyle={{ x: 0, y: 10, opacity: 0, scale: 0.95 }} gap="$4" ><Dialog.Title>Edit profile</Dialog.Title><Dialog.Description>Make changes to your profile here. Click save when you're done.</Dialog.Description><Fieldset gap="$4" horizontal><Label width={160} justifyContent="flex-end" htmlFor="name">Name</Label><Input flex={1} id="name" defaultValue="Nate Wienert" /></Fieldset><Fieldset gap="$4" horizontal><Label width={160} justifyContent="flex-end" htmlFor="username"><TooltipSimple label="Pick your favorite" placement="bottom-start"><Paragraph>Food</Paragraph></TooltipSimple></Label><SelectDemoItem /></Fieldset><XStack alignSelf="flex-end" gap="$4"><DialogInstance /><Dialog.Close displayWhenAdapted asChild><Button theme="active" aria-label="Close">Save changes</Button></Dialog.Close></XStack><Unspaced><Dialog.Close asChild><Button position="absolute" top="$3" right="$3" size="$2" circular icon={X} /></Dialog.Close></Unspaced></Dialog.Content></Dialog.Portal></Dialog>)}
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 does not work on native, instead you can Adapt it to a Sheet as shown in the demo code above.
Installation
Dialog is already installed in tamagui
, or you can install it independently:
yarn @tamagui/dialog
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></Dialog>)
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.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="sm"><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.
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>)}
Previous
AlertDialog
Next
Popover