Dialog
Compound confirmation modal.
Installation
npx axiom add dialogpnpm dlx axiom add dialogyarn dlx axiom add dialogbun x axiom add dialogAlso copies: portal, overlay, button, use-overlay-back-handler, which closes the dialog on a back action (Android back button, and with a navigator the back gesture and header back button). See navigation.
Installs: react-native-reanimated, react-native-worklets.
The open state and the enter and exit animation live in use-dialog.ts. The surface color comes from dialog.default.default.background in the component tokens.
Usage
import { Dialog } from "@/components/ui/dialog";
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
<Button variant="ghost">Delete</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Title>Delete this draft?</Dialog.Title>
<Dialog.Description>You can't undo this.</Dialog.Description>
<Dialog.Actions>
<Dialog.Cancel>Cancel</Dialog.Cancel>
<Dialog.Action destructive onPress={remove}>
Delete
</Dialog.Action>
</Dialog.Actions>
</Dialog.Content>
</Dialog.Root>;Props
Dialog.Root
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled visibility. |
defaultOpen | boolean | false | Initial visibility when the dialog manages its own state. |
onOpenChange | (open: boolean) => void | — | Called when the dialog opens or closes. |
children | ReactNode | — | Trigger and Content. |
Dialog.Trigger
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Uses the child as the trigger. |
It also accepts every Tappable prop.
Dialog.Content
| Prop | Type | Default | Description |
|---|---|---|---|
dismissible | boolean | true | Closes on backdrop press and Android back. false when the user must pick an action. |
media | ReactNode | — | Icon or illustration above the title. |
width | number | 290 | Dialog width, capped by the screen margins. |
onDismiss | () => void | — | Called after the close animation. |
children | ReactNode | — | Title, description, custom content, actions. |
style | StyleProp<ViewStyle> | — | Extra styles for the dialog surface. |
Focus moves into the dialog when it opens and returns to the trigger when it closes. Screen readers can’t reach the screen behind it.
Dialog.Title, Dialog.Description
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Title: the question, in a few words. Description: the consequence. Both are announced when the dialog opens. |
Dialog.Actions
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'horizontal' | horizontal for two short actions, vertical for three actions or long labels. |
children | ReactNode | — | Dialog.Cancel and Dialog.Action. |
Dialog.Action, Dialog.Cancel
Button props, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
destructive | boolean | false | On Action: red solid button, for irreversible actions. |
closeOnPress | boolean | true | Closes the dialog after onPress. Set to false with an async onPress and loading to keep it open while it runs. |
Dialog.Cancel is an outline button that closes the dialog; it is also what a back action triggers.
Use cases
Confirm an irreversible action
State what will be lost, and name the action with the verb, not “OK”.
<Dialog.Content>
<Dialog.Title>Remove {member.firstName} from the team?</Dialog.Title>
<Dialog.Description>
They'll lose access to {count} shared projects.
</Dialog.Description>
<Dialog.Actions>
<Dialog.Cancel>Cancel</Dialog.Cancel>
<Dialog.Action destructive onPress={() => removeMember(member.id)}>
Remove
</Dialog.Action>
</Dialog.Actions>
</Dialog.Content>Unsaved changes
Three choices stacked vertically, the safe one at the bottom. discard can leave the screen with router.back(): the dialog closes and the navigation goes through (see useOverlayBackHandler).
<Dialog.Content dismissible={false}>
<Dialog.Title>Save changes?</Dialog.Title>
<Dialog.Actions orientation="vertical">
<Dialog.Action onPress={save}>Save</Dialog.Action>
<Dialog.Action variant="ghost" destructive onPress={discard}>
Discard
</Dialog.Action>
<Dialog.Cancel>Keep editing</Dialog.Cancel>
</Dialog.Actions>
</Dialog.Content>Action that takes time
closeOnPress={false} with loading: the dialog stays until the request succeeds, and shows an error in place if it fails.
<Dialog.Action
closeOnPress={false}
loading={mutation.isPending}
onPress={async () => {
await mutation.mutateAsync();
setOpen(false);
}}
>
Sign out
</Dialog.Action>When to use it
A dialog interrupts the user. Keep it for decisions that need an answer before going further, such as confirming a deletion. For choosing among options, a BottomSheet or a Menu keeps more context.
Related
- BottomSheet
- Menu
- Snackbar, to offer Undo instead of asking first