Alert Dialog
Dialogs inform users about a task and can contain critical information, require decisions, or involve multiple tasks.
'use client';
import * as React from 'react';
import { AlertDialog } from '@base_ui/react/AlertDialog';
import classes from './styles.module.css';
export default function AlertDialogIntroduction() {
return (
<AlertDialog.Root>
<AlertDialog.Trigger className={classes.trigger}>
Subscribe
</AlertDialog.Trigger>
<AlertDialog.Backdrop className={classes.backdrop} />
<AlertDialog.Popup className={classes.popup}>
<AlertDialog.Title className={classes.title}>Subscribe</AlertDialog.Title>
<AlertDialog.Description>
Are you sure you want to subscribe?
</AlertDialog.Description>
<div className={classes.controls}>
<AlertDialog.Close className={classes.close}>Yes</AlertDialog.Close>
<AlertDialog.Close className={classes.close}>No</AlertDialog.Close>
</div>
</AlertDialog.Popup>
</AlertDialog.Root>
);
}
Installation
Base UI components are all available as a single package.
npm install @base_ui/react
Once you have the package installed, import the component.
import { AlertDialog } from '@base_ui/react/AlertDialog';
Anatomy
Alert Dialogs are implemented using a collection of related components:
<AlertDialog.Root />
is a top-level component that facilitates communication between other components. It does not render to the DOM.<AlertDialog.Popup />
is the alert dialog panel itself.<AlertDialog.Backdrop />
is the background element appearing when a popup is visible. Use it to indicate that the page is inert. The Backdrop must be a sibling of the Popup component.<AlertDialog.Trigger />
is the component (a button by default) that, when clicked, shows the popup. When it's not provided, the visibility of the Alert Dialog can be controlled with itsopen
prop (see Controlled vs. uncontrolled behavior).<AlertDialog.Close />
renders a button that closes the popup. You can attach your own click handlers to it to perform additional actions.<AlertDialog.Title />
is an header element displaying the title of the alert dialog. It is referenced in the Dialog's ARIA attributes to properly announce it.<AlertDialog.Description />
is an element describing of the dialog. It is referenced in the Dialog's ARIA attributes to properly announce it.
Alert dialogs vs. dialogs
The Alert Dialog is in many ways similar to the Dialog component. Alert dialogs should be used in cases where the normal user's workflow needs to be interrupted to get a response. Therefore alert dialogs are always modal and cannot be dismissed any other way than by pressing a button inside them.
Controlled vs. uncontrolled behavior
The simplest way to control the visibility of the alert dialog is to use the AlertDialog.Trigger
and AlertDialog.Close
components.
You can set the initial state with the defaultOpen
prop.
Doing so ensures that the accessibity attributes are set correctly so that the trigger button is approriately announced by assistive technologies.
If you need to control the visibility programmatically from the outside, use the value
prop.
You can still use the AlertDialog.Trigger
and AlertDialog.Close
components (though it's not necessary), but you need to make sure to create a handler for the onOpenChange
event and update the state manually.
Nested dialogs
An alert dialog can open another dialog (or alert dialog). At times, it may be useful to know how may open sub-dialogs a given alert dialog has. One example of this could be styling the bottom dialog in a way they appear below the top-most one.
The number of open child dialogs is present in the data-nested-dialogs
attribute and in the --nested-dialogs
CSS variable on the <AlertDialog.Popup>
component.
'use client';
import * as React from 'react';
import { AlertDialog as BaseAlertDialog } from '@base_ui/react/AlertDialog';
import { styled } from '@mui/system';
export default function NestedAlertDialogs() {
return (
<BaseAlertDialog.Root>
<Trigger>Open</Trigger>
<Backdrop />
<Popup>
<Title>Alert Dialog 1</Title>
<Controls>
<BaseAlertDialog.Root>
<Trigger>Open Nested</Trigger>
<Backdrop />
<Popup>
<Title>Alert Dialog 2</Title>
<Controls>
<BaseAlertDialog.Root>
<Trigger>Open Nested</Trigger>
<Backdrop />
<Popup>
<Title>Alert Dialog 3</Title>
<Controls>
<Close>Close</Close>
</Controls>
</Popup>
</BaseAlertDialog.Root>
<Close>Close</Close>
</Controls>
</Popup>
</BaseAlertDialog.Root>
<Close>Close</Close>
</Controls>
</Popup>
</BaseAlertDialog.Root>
);
}
const grey = {
900: '#0f172a',
800: '#1e293b',
700: '#334155',
500: '#64748b',
300: '#cbd5e1',
200: '#e2e8f0',
100: '#f1f5f9',
50: '#f8fafc',
};
const Popup = styled(BaseAlertDialog.Popup)(
({ theme }) => `
--transition-duration: 150ms;
background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
min-width: 400px;
border-radius: 4px;
box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
position: fixed;
top: 50%;
left: 50%;
font-family: IBM Plex Sans;
padding: 16px;
z-index: 2100;
transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs))))
translateY(calc(-30px * var(--nested-dialogs)));
visibility: hidden;
opacity: 0.5;
transition:
transform var(--transition-duration) ease-in,
opacity var(--transition-duration) ease-in,
visibility var(--transition-duration) step-end;
&[data-open] {
@starting-style {
& {
transform: translate(-50%, -35%) scale(0.8) translateY(0);
opacity: 0.5;
}
}
visibility: visible;
opacity: 1;
transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs))))
translateY(calc(-30px * var(--nested-dialogs)));
transition:
transform var(--transition-duration) ease-out,
opacity var(--transition-duration) ease-out,
visibility var(--transition-duration) step-start;
}
`,
);
const Title = styled(BaseAlertDialog.Title)`
font-size: 1.25rem;
`;
const Trigger = styled(BaseAlertDialog.Trigger)(
({ theme }) => `
background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
padding: 8px 16px;
border-radius: 4px;
border: none;
font-family:
"IBM Plex Sans",
sans-serif;
&:hover {
background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
}
`,
);
const Backdrop = styled(BaseAlertDialog.Backdrop)`
background-color: rgb(0 0 0 / 0.2);
position: fixed;
inset: 0;
z-index: 2000;
backdrop-filter: blur(0);
opacity: 0;
transition-property: opacity, backdrop-filter;
transition-duration: 250ms;
transition-timing-function: ease-in;
&[data-open] {
backdrop-filter: blur(6px);
opacity: 1;
transition-timing-function: ease-out;
}
&[data-entering] {
backdrop-filter: blur(0);
opacity: 0;
}
`;
const Close = styled(BaseAlertDialog.Close)(
({ theme }) => `
background-color: transparent;
border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
padding: 8px 16px;
border-radius: 4px;
font-family: IBM Plex Sans, sans-serif;
min-width: 80px;
&:hover {
background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
}
`,
);
const Controls = styled('div')(
({ theme }) => `
display: flex;
flex-direction: row-reverse;
background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
gap: 8px;
padding: 16px;
margin: 32px -16px -16px;
`,
);
Note that when dialogs are nested, only the bottom-most backdrop is rendered.
Animation
The <AlertDialog.Popup>
and <AlertDialog.Backdrop>
components support transitions on entry and exit.
CSS animations and transitions are supported out of the box. If a component has a transition or animation applied to it when it closes, it will be unmounted only after the animation finishes.
As this detection of exit animations requires an extra render, you may opt out of it by setting the animated
prop on Popup and Backdrop to false
.
We also recommend doing so in automated tests, to avoid asynchronous behavior and make testing easier.
Alternatively, you can use JavaScript-based animations with a library like framer-motion, React Spring, or similar.
With this approach set the keepMounted
to true
and let the animation library control mounting and unmounting.
CSS transitions
Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior:
Styles need to be applied in three states:
- The exiting styles, placed on the base element class
- The open styles, placed on the base element class with
[data-state="open"]
- The entering styles, placed on the base element class with
[data-entering]
'use client';
import * as React from 'react';
import { AlertDialog as BaseAlertDialog } from '@base_ui/react/AlertDialog';
import { styled } from '@mui/system';
export default function AlertDialogWithTransitions() {
return (
<BaseAlertDialog.Root>
<Trigger>Open</Trigger>
<Popup>
<Title>Animated alert dialog</Title>
<BaseAlertDialog.Description>
This alert dialog uses CSS transitions on entry and exit.
</BaseAlertDialog.Description>
<Controls>
<Close>Close</Close>
</Controls>
</Popup>
<Backdrop />
</BaseAlertDialog.Root>
);
}
const grey = {
900: '#0f172a',
800: '#1e293b',
700: '#334155',
500: '#64748b',
300: '#cbd5e1',
200: '#e2e8f0',
100: '#f1f5f9',
50: '#f8fafc',
};
const Popup = styled(BaseAlertDialog.Popup)(
({ theme }) => `
background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
min-width: 400px;
border-radius: 4px;
box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
position: fixed;
top: 50%;
left: 50%;
font-family: IBM Plex Sans;
padding: 16px;
z-index: 2100;
transition-property: opacity, transform;
transition-duration: 150ms;
transition-timing-function: ease-in;
opacity: 0;
transform: translate(-50%, -35%) scale(0.8);
&[data-open] {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
transition-timing-function: ease-out;
}
&[data-entering] {
opacity: 0;
transform: translate(-50%, -35%) scale(0.8);
}
`,
);
const Backdrop = styled(BaseAlertDialog.Backdrop)`
background-color: rgb(0 0 0 / 0.2);
position: fixed;
inset: 0;
z-index: 2000;
backdrop-filter: blur(0);
opacity: 0;
transition-property: opacity, backdrop-filter;
transition-duration: 250ms;
transition-timing-function: ease-in;
&[data-open] {
backdrop-filter: blur(6px);
opacity: 1;
transition-timing-function: ease-out;
}
&[data-entering] {
backdrop-filter: blur(0);
opacity: 0;
}
`;
const Title = styled(BaseAlertDialog.Title)`
font-size: 1.25rem;
`;
const Trigger = styled(BaseAlertDialog.Trigger)(
({ theme }) => `
background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
padding: 8px 16px;
border-radius: 4px;
border: none;
font-family:
"IBM Plex Sans",
sans-serif;
&:hover {
background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
}
`,
);
const Close = styled(BaseAlertDialog.Close)(
({ theme }) => `
background-color: transparent;
border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
padding: 8px 16px;
border-radius: 4px;
font-family: IBM Plex Sans, sans-serif;
min-width: 80px;
&:hover {
background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
}
`,
);
const Controls = styled('div')(
({ theme }) => `
display: flex;
flex-direction: row-reverse;
background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
gap: 8px;
padding: 16px;
margin: 32px -16px -16px;
`,
);
In newer browsers, there is a feature called @starting-style
which allows transitions to occur on open for conditionally-mounted components:
CSS animations
CSS animations can also be used, requiring only two separate declarations:
JavaScript animations
The keepMounted
prop lets an external library control the mounting, for example framer-motion
's AnimatePresence
component.
Animation states
Four states are available as data attributes to animate the dialog, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the keepMounted
prop.
[data-state="open"]
-open
state istrue
.[data-state="closed"]
-open
state isfalse
. Can still be mounted to the DOM if closing.[data-entering]
- the popup was just inserted to the DOM. The attribute is removed 1 animation frame later. Enables "starting styles" upon insertion for conditional rendering.[data-exiting]
- the popup is in the process of being removed from the DOM, but is still mounted.
Composing a custom React component
Use the render
prop to override the rendered element:
Accessibility
Using the <AlertDialog.Trigger>
sets the required accessibility attributes on the trigger button.
If you prefer controlling the open state differently, you need to apply these attributes on your own:
API Reference
AlertDialogBackdrop
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
keepMounted | bool | false | If true , the backdrop element is kept in the DOM when closed. |
render | union | A function to customize rendering of the component. |
AlertDialogClose
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
AlertDialogDescription
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
AlertDialogPopup
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
container | union | The container element to which the popup is appended to. | |
keepMounted | bool | false | If true , the dialog element is kept in the DOM when closed. |
render | union | A function to customize rendering of the component. |
AlertDialogRoot
Prop | Type | Default | Description |
---|---|---|---|
animated | bool | true | If true , the dialog supports CSS-based animations and transitions. It is kept in the DOM until the animation completes. |
defaultOpen | bool | Determines whether the dialog is initally open. This is an uncontrolled equivalent of the open prop. | |
onOpenChange | func | Callback invoked when the dialog is being opened or closed. | |
open | bool | Determines whether the dialog is open. |
AlertDialogTitle
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
AlertDialogTrigger
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |