# Alert Dialog

A modal dialog that interrupts the user with important content and expects a response.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export default function AlertDialogDemo() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline" />}>Show dialog</AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
          <AlertDialogDescription>
            This action cannot be undone. This will permanently delete your account from our
            servers.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Continue</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex clsx
   ```

2. Set up the token layer and the StyleX compiler, if you have not already. See
   [Installation](https://carbon.style/docs/installation), and copy `state-styles.ts` from the same page.
   Copy `states.ts` for the shared popup surface and install the
   [Button](https://carbon.style/docs/components/button) for the action and cancel controls. Include
   `interop.css` for media icon sizing.

3. Copy and paste the following code into your project.

   ```tsx title="components/ui/alert-dialog.tsx"
   "use client"

   import { useRender } from "@base-ui/react/use-render"
   import { createContext, use } from "react"
   import type { ComponentProps, CSSProperties } from "react"
   import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"
   import { clsx } from "clsx"

   import { Button } from "@/components/ui/button"
   import { stateStyles } from "@/lib/state-styles"
   import { breakpoints } from "@/theme/breakpoints.stylex"
   import { states } from "@/theme/states"
   import {
     color,
     duration,
     ease,
     layer,
     leading,
     radius,
     shadow,
     space,
     text,
   } from "@/theme/tokens.stylex"

   const styles = stylex.create({
     overlayHidden: { opacity: 0 },
     hidden: { opacity: 0, transform: "scale(0.95)" },
     overlay: {
       position: "fixed",
       inset: 0,
       zIndex: layer.popup,
       backgroundColor: color.overlay,
       backdropFilter: "blur(2px)",
       transitionProperty: "opacity",
       transitionDuration: {
         default: duration.modal,
         "@media (prefers-reduced-motion: reduce)": "0s",
       },
       transitionTimingFunction: ease.standard,
     },
     viewport: {
       position: "fixed",
       inset: 0,
       zIndex: layer.popup,
       display: "grid",
       alignItems: "safe center",
       justifyItems: "center",
       overflow: "auto",
       padding: space.s4,
     },
     content: {
       display: "grid",
       gap: space.s4,
       width: "100%",
       borderRadius: radius.xl,
       padding: space.s5,
       boxShadow: shadow.popupLifted,
       fontSize: text.sm,
       lineHeight: leading.sm,
       transitionProperty: "opacity, transform",
       transitionDuration: {
         default: duration.modal,
         "@media (prefers-reduced-motion: reduce)": "0s",
       },
       transitionTimingFunction: ease.standard,
     },
     header: { display: "grid", rowGap: space.s1_5, justifyItems: "center", textAlign: "center" },
     headerInline: {
       gridTemplateColumns: { default: null, [breakpoints.md]: "auto 1fr" },
       justifyItems: { default: "center", [breakpoints.md]: "start" },
       textAlign: { default: "center", [breakpoints.md]: "start" },
     },
     textColumn: { gridColumnStart: { default: null, [breakpoints.md]: 2 } },
     media: {
       width: space.s10,
       height: space.s10,
       marginBlockEnd: space.s2,
       "--carbon-icon-size": space.s6,
     },
     mediaInline: {
       gridColumnStart: { default: null, [breakpoints.md]: 1 },
       gridRowStart: { default: null, [breakpoints.md]: "1" },
       gridRowEnd: { default: null, [breakpoints.md]: "span 2" },
       marginBlockEnd: { default: space.s2, [breakpoints.md]: 0 },
       marginInlineEnd: { default: null, [breakpoints.md]: space.s4 },
     },
     title: { fontSize: text.base, lineHeight: leading.base, fontWeight: 500 },
     description: { color: color.mutedForeground, textWrap: "balance" },
     footer: {
       display: "flex",
       flexDirection: { default: "column-reverse", [breakpoints.md]: "row" },
       justifyContent: { default: null, [breakpoints.md]: "flex-end" },
       gap: space.s2,
       marginInline: `calc(-1 * ${space.s5})`,
       marginBlockStart: `calc(${space.s1} - ${radius.xl})`,
       marginBlockEnd: `calc(-1 * ${space.s5})`,
       borderEndStartRadius: radius.xl,
       borderEndEndRadius: radius.xl,
     },
     footerSplit: { display: "grid", gridTemplateColumns: "1fr 1fr" },
   })

   const sizes = stylex.create({
     default: { maxWidth: { default: "20rem", [breakpoints.md]: "24rem" } },
     sm: { maxWidth: "20rem" },
   })

   const alertDialogStyles = { surface: states.popupSurface, base: styles.content, sizes }

   type AlertDialogSize = keyof typeof sizes

   const AlertDialogContext = createContext<{ size: AlertDialogSize }>({ size: "default" })

   type Overrides = { xstyle?: StyleXStyles; className?: string }
   type StateOverrides = Overrides & { style?: CSSProperties }

   function AlertDialog<Payload>(props: AlertDialogPrimitive.Root.Props<Payload>) {
     return <AlertDialogPrimitive.Root {...props} />
   }

   function AlertDialogTrigger(props: AlertDialogPrimitive.Trigger.Props) {
     return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
   }

   function AlertDialogPortal(props: AlertDialogPrimitive.Portal.Props) {
     return <AlertDialogPrimitive.Portal {...props} />
   }

   type AlertDialogOverlayProps = Omit<AlertDialogPrimitive.Backdrop.Props, "className" | "style"> &
     StateOverrides

   function AlertDialogOverlay({ xstyle, className, style, ...props }: AlertDialogOverlayProps) {
     return (
       <AlertDialogPrimitive.Backdrop
         data-slot="alert-dialog-overlay"
         {...props}
         {...stateStyles(
           (state) => [
             styles.overlay,
             (state.transitionStatus === "starting" || state.transitionStatus === "ending") &&
               styles.overlayHidden,
             xstyle,
           ],
           { className, style },
         )}
       />
     )
   }

   type AlertDialogViewportProps = Omit<AlertDialogPrimitive.Viewport.Props, "className"> & Overrides

   function AlertDialogViewport({ xstyle, className, ...props }: AlertDialogViewportProps) {
     const sx = stylex.props(styles.viewport, xstyle)
     return (
       <AlertDialogPrimitive.Viewport
         data-slot="alert-dialog-viewport"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   type AlertDialogContentProps = Omit<AlertDialogPrimitive.Popup.Props, "className" | "style"> &
     StateOverrides & {
       size?: AlertDialogSize
       overlayProps?: AlertDialogOverlayProps
       viewportProps?: AlertDialogViewportProps
     }

   function AlertDialogContent({
     xstyle,
     className,
     style,
     size = "default",
     overlayProps,
     viewportProps,
     children,
     ...props
   }: AlertDialogContentProps) {
     return (
       <AlertDialogPortal>
         <AlertDialogOverlay {...overlayProps} />
         <AlertDialogViewport {...viewportProps}>
           <AlertDialogPrimitive.Popup
             data-slot="alert-dialog-content"
             data-size={size}
             {...props}
             {...stateStyles(
               (state) => [
                 states.popupSurface,
                 styles.content,
                 sizes[size],
                 (state.transitionStatus === "starting" || state.transitionStatus === "ending") &&
                   styles.hidden,
                 xstyle,
               ],
               { className, style },
             )}
           >
             <AlertDialogContext value={{ size }}>{children}</AlertDialogContext>
           </AlertDialogPrimitive.Popup>
         </AlertDialogViewport>
       </AlertDialogPortal>
     )
   }

   function AlertDialogHeader({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"div"> & Overrides) {
     const { size } = use(AlertDialogContext)
     const sx = stylex.props(styles.header, size === "default" && styles.headerInline, xstyle)
     return useRender({
       defaultTagName: "div",
       render,
       ref,
       props: {
         "data-slot": "alert-dialog-header",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   function AlertDialogMedia({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"div"> & Overrides) {
     const { size } = use(AlertDialogContext)
     const sx = stylex.props(
       states.iconTile,
       styles.media,
       size === "default" && styles.mediaInline,
       xstyle,
     )
     return useRender({
       defaultTagName: "div",
       render,
       ref,
       props: {
         "data-slot": "alert-dialog-media",
         ...sx,
         ...props,
         className: clsx("carbon-icons", sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   function AlertDialogTitle({
     xstyle,
     className,
     ...props
   }: AlertDialogPrimitive.Title.Props & Overrides) {
     const { size } = use(AlertDialogContext)
     const sx = stylex.props(styles.title, size === "default" && styles.textColumn, xstyle)
     return (
       <AlertDialogPrimitive.Title
         data-slot="alert-dialog-title"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   function AlertDialogDescription({
     xstyle,
     className,
     ...props
   }: AlertDialogPrimitive.Description.Props & Overrides) {
     const { size } = use(AlertDialogContext)
     const sx = stylex.props(styles.description, size === "default" && styles.textColumn, xstyle)
     return (
       <AlertDialogPrimitive.Description
         data-slot="alert-dialog-description"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   function AlertDialogFooter({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"div"> & Overrides) {
     const { size } = use(AlertDialogContext)
     const sx = stylex.props(
       states.trayFooter,
       styles.footer,
       size === "sm" && styles.footerSplit,
       xstyle,
     )
     return useRender({
       defaultTagName: "div",
       render,
       ref,
       props: {
         "data-slot": "alert-dialog-footer",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   function AlertDialogAction(props: ComponentProps<typeof Button>) {
     return <Button data-slot="alert-dialog-action" {...props} />
   }

   type AlertDialogCancelProps = AlertDialogPrimitive.Close.Props &
     Pick<ComponentProps<typeof Button>, "variant" | "size" | "xstyle">

   function AlertDialogCancel({
     variant = "secondary",
     size,
     xstyle,
     ...props
   }: AlertDialogCancelProps) {
     return (
       <AlertDialogPrimitive.Close
         data-slot="alert-dialog-cancel"
         render={<Button variant={variant} size={size} xstyle={xstyle} />}
         {...props}
       />
     )
   }

   export {
     AlertDialog,
     AlertDialogAction,
     AlertDialogCancel,
     AlertDialogContent,
     AlertDialogDescription,
     AlertDialogFooter,
     AlertDialogHeader,
     AlertDialogMedia,
     AlertDialogOverlay,
     AlertDialogPortal,
     alertDialogStyles,
     AlertDialogTitle,
     AlertDialogTrigger,
     AlertDialogViewport,
     type AlertDialogSize,
   }
   ```

4. Update the import paths to match your project setup.

## Usage

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
```

```tsx
<AlertDialog>
  <AlertDialogTrigger render={<Button variant="outline" />}>Show dialog</AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
      <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction>Continue</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>
```

An alert dialog is always modal and never closes on an outside press or on
Escape. Every path out of it is a button you render.

## Composition

```text
AlertDialog
├── AlertDialogTrigger
└── AlertDialogContent     portal + overlay + viewport + popup
    ├── AlertDialogHeader
    │   ├── AlertDialogMedia
    │   ├── AlertDialogTitle
    │   └── AlertDialogDescription
    └── AlertDialogFooter
        ├── AlertDialogCancel
        └── AlertDialogAction
```

`AlertDialogFooter` is a tray, and the content above it is a panel raised out
of it. Keep the footer the last child of `AlertDialogContent`. It bleeds to the
popup's edges and draws the panel's bottom edge itself, so the panel needs no
wrapper.

## Size

Use the `size` prop on `AlertDialogContent`. `sm` stays narrow at every width
and splits its footer into two equal columns.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
  type AlertDialogSize,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { HStack } from "@/components/ui/stack"

const sizes: AlertDialogSize[] = ["default", "sm"]

export default function AlertDialogSizeDemo() {
  return (
    <HStack gap={2}>
      {sizes.map((size) => (
        <AlertDialog key={size}>
          <AlertDialogTrigger render={<Button variant="outline" />}>{size}</AlertDialogTrigger>
          <AlertDialogContent size={size}>
            <AlertDialogHeader>
              <AlertDialogTitle>Publish this release?</AlertDialogTitle>
              <AlertDialogDescription>
                Everyone on your team will be notified.
              </AlertDialogDescription>
            </AlertDialogHeader>
            <AlertDialogFooter>
              <AlertDialogCancel>Cancel</AlertDialogCancel>
              <AlertDialogAction>Publish</AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        </AlertDialog>
      ))}
    </HStack>
  )
}
```

## Media

Use `AlertDialogMedia` for an icon above the title. At `default` size it moves
beside the text from the `md` breakpoint up.

```tsx
import { CircleFadingPlus } from "lucide-react"
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export default function AlertDialogMediaDemo() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline" />}>Share project</AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogMedia>
            <CircleFadingPlus />
          </AlertDialogMedia>
          <AlertDialogTitle>Share this project?</AlertDialogTitle>
          <AlertDialogDescription>
            Anyone with the link will be able to view and edit it.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Share</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Destructive

Pass `variant="destructive"` to `AlertDialogAction`, and tint the media through
`xstyle` to match.

```tsx
import * as stylex from "@stylexjs/stylex"
import { Trash2 } from "lucide-react"
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { color } from "@/theme/tokens.stylex"

const styles = stylex.create({
  media: { backgroundColor: color.buttonDestructiveBg, color: color.destructive },
})

export default function AlertDialogDestructiveDemo() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="destructive" />}>Delete chat</AlertDialogTrigger>
      <AlertDialogContent size="sm">
        <AlertDialogHeader>
          <AlertDialogMedia xstyle={styles.media}>
            <Trash2 />
          </AlertDialogMedia>
          <AlertDialogTitle>Delete chat?</AlertDialogTitle>
          <AlertDialogDescription>
            This permanently deletes the conversation and any memories saved during it.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction variant="destructive">Delete</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

> **Note**
>
> `AlertDialogAction` does not close the dialog. It is a plain `Button`, so close
> it from `onClick` or once the work it starts settles. That way a failed delete
> leaves the dialog open.

## API reference

Pass `render` to replace a part's element or compose it with another component.

Every part wraps the Base UI `AlertDialog` part of the same name and accepts
its props. See the
[Base UI reference](https://base-ui.com/react/components/alert-dialog) for the
full list. Each styled part also takes `xstyle` and `className`.

### AlertDialogContent

| Prop            | Type                       | Default     |
| --------------- | -------------------------- | ----------- |
| `size`          | `"default" \| "sm"`        | `"default"` |
| `overlayProps`  | `AlertDialogOverlayProps`  | —           |
| `viewportProps` | `AlertDialogViewportProps` | —           |

`AlertDialogContent` passes `size` to the header, media and footer through
context, so each of them changes layout without an ancestor selector.

### AlertDialogAction

Takes every `Button` prop. `variant` defaults to `"default"`.

### AlertDialogCancel

Renders `AlertDialog.Close` as a `Button` and closes the dialog on press.

| Prop      | Type            | Default       |
| --------- | --------------- | ------------- |
| `variant` | `ButtonVariant` | `"secondary"` |
| `size`    | `ButtonSize`    | —             |

### alertDialogStyles

| Key       | Contents                                             |
| --------- | ---------------------------------------------------- |
| `surface` | The shared popup surface: fill, foreground, hairline |
| `base`    | The popup box: radius, padding, grid, shadow, motion |
| `sizes`   | `default` and `sm`, each a `maxWidth`                |

### Data attributes

| Attribute                                  | On                | Effect                                         |
| ------------------------------------------ | ----------------- | ---------------------------------------------- |
| `data-slot="alert-dialog-content"`         | the popup         | Popup slot                                     |
| `data-slot="alert-dialog-overlay"`         | the scrim         | Backdrop surface                               |
| `data-size`                                | the popup         | The `size` prop, for selecting on from outside |
| `data-starting-style`, `data-ending-style` | popup and overlay | Base UI writes them while the dialog animates  |
| `data-open`, `data-closed`                 | popup and overlay | Base UI writes them                            |
