Alert Dialog
A modal dialog that interrupts the user with important content and expects a response.
Installation
Install the component Alert Dialog in your project using the CLI.
Alert Dialog.tsx
pnpm dlx behsseui@latest add AlertDialogInstall the component manually.
Create a ui folder at the root of the project, then a component folder inside it, and finally a Alert Dialog.tsx file in that folder.
Copy and paste the following code into your project.
ui/components/AlertDialog.tsx
1"use client"23import type { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from "react"4import { createContext, useContext, useState, useEffect, useCallback } from "react"5import { cva, type VariantProps } from "class-variance-authority"6import { cn } from "@/lib/utils"78const alertDialogOverlayVariants = cva(9 "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"10)1112const alertDialogContentVariants = cva(13 "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg"14)1516const alertDialogHeaderVariants = cva(17 "flex flex-col space-y-2 text-center sm:text-left"18)1920const alertDialogFooterVariants = cva(21 "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2"22)2324const alertDialogTitleVariants = cva(25 "text-lg font-semibold"26)2728const alertDialogDescriptionVariants = cva(29 "text-sm text-muted-foreground"30)3132const alertDialogActionVariants = cva(33 "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-9 px-4 py-2"34)3536const alertDialogCancelVariants = cva(37 "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2 mt-2 sm:mt-0"38)3940type AlertDialogContextType = {41 open: boolean42 onOpenChange: (open: boolean) => void43}4445const AlertDialogContext = createContext<AlertDialogContextType | undefined>(undefined)4647function useAlertDialog() {48 const context = useContext(AlertDialogContext)49 if (!context) {50 throw new Error("AlertDialog components must be used within an AlertDialog")51 }52 return context53}5455type AlertDialogProps = {56 children: ReactNode57 open?: boolean58 defaultOpen?: boolean59 onOpenChange?: (open: boolean) => void60}6162export function AlertDialog({63 children,64 open: controlledOpen,65 defaultOpen = false,66 onOpenChange,67}: AlertDialogProps) {68 const [internalOpen, setInternalOpen] = useState(defaultOpen)6970 const isControlled = controlledOpen !== undefined71 const open = isControlled ? controlledOpen : internalOpen7273 const handleOpenChange = useCallback((newOpen: boolean) => {74 if (!isControlled) {75 setInternalOpen(newOpen)76 }77 onOpenChange?.(newOpen)78 }, [isControlled, onOpenChange])7980 return (81 <AlertDialogContext.Provider value={{ open, onOpenChange: handleOpenChange }}>82 {children}83 </AlertDialogContext.Provider>84 )85}8687type AlertDialogTriggerProps = {88 children: ReactNode89 className?: string90} & ButtonHTMLAttributes<HTMLButtonElement>9192export function AlertDialogTrigger({ children, className, ...props }: AlertDialogTriggerProps) {93 const { onOpenChange } = useAlertDialog()9495 return (96 <button97 type="button"98 className={className}99 onClick={() => onOpenChange(true)}100 {...props}101 >102 {children}103 </button>104 )105}106107type AlertDialogPortalProps = {108 children: ReactNode109}110111export function AlertDialogPortal({ children }: AlertDialogPortalProps) {112 const { open } = useAlertDialog()113114 if (!open) return null115116 return <>{children}</>117}118119type AlertDialogOverlayProps = {120 className?: string121} & HTMLAttributes<HTMLDivElement>122123export function AlertDialogOverlay({ className, ...props }: AlertDialogOverlayProps) {124 const { open, onOpenChange } = useAlertDialog()125126 return (127 <div128 className={cn(alertDialogOverlayVariants(), className)}129 data-state={open ? "open" : "closed"}130 onClick={() => onOpenChange(false)}131 {...props}132 />133 )134}135136type AlertDialogContentProps = {137 children: ReactNode138 className?: string139} & HTMLAttributes<HTMLDivElement>140141export function AlertDialogContent({ children, className, ...props }: AlertDialogContentProps) {142 const { open, onOpenChange } = useAlertDialog()143144 // Handle escape key145 useEffect(() => {146 const handleEscape = (e: KeyboardEvent) => {147 if (e.key === "Escape") {148 onOpenChange(false)149 }150 }151152 if (open) {153 document.addEventListener("keydown", handleEscape)154 document.body.style.overflow = "hidden"155 }156157 return () => {158 document.removeEventListener("keydown", handleEscape)159 document.body.style.overflow = ""160 }161 }, [open, onOpenChange])162163 return (164 <AlertDialogPortal>165 <AlertDialogOverlay />166 <div167 role="alertdialog"168 className={cn(alertDialogContentVariants(), className)}169 data-state={open ? "open" : "closed"}170 onClick={(e) => e.stopPropagation()}171 {...props}172 >173 {children}174 </div>175 </AlertDialogPortal>176 )177}178179type AlertDialogHeaderProps = {180 children: ReactNode181 className?: string182} & HTMLAttributes<HTMLDivElement>183184export function AlertDialogHeader({ children, className, ...props }: AlertDialogHeaderProps) {185 return (186 <div className={cn(alertDialogHeaderVariants(), className)} {...props}>187 {children}188 </div>189 )190}191192type AlertDialogFooterProps = {193 children: ReactNode194 className?: string195} & HTMLAttributes<HTMLDivElement>196197export function AlertDialogFooter({ children, className, ...props }: AlertDialogFooterProps) {198 return (199 <div className={cn(alertDialogFooterVariants(), className)} {...props}>200 {children}201 </div>202 )203}204205type AlertDialogTitleProps = {206 children: ReactNode207 className?: string208} & HTMLAttributes<HTMLHeadingElement>209210export function AlertDialogTitle({ children, className, ...props }: AlertDialogTitleProps) {211 return (212 <h2 className={cn(alertDialogTitleVariants(), className)} {...props}>213 {children}214 </h2>215 )216}217218type AlertDialogDescriptionProps = {219 children: ReactNode220 className?: string221} & HTMLAttributes<HTMLParagraphElement>222223export function AlertDialogDescription({ children, className, ...props }: AlertDialogDescriptionProps) {224 return (225 <p className={cn(alertDialogDescriptionVariants(), className)} {...props}>226 {children}227 </p>228 )229}230231type AlertDialogActionProps = {232 children: ReactNode233 className?: string234} & ButtonHTMLAttributes<HTMLButtonElement>235236export function AlertDialogAction({ children, className, onClick, ...props }: AlertDialogActionProps) {237 const { onOpenChange } = useAlertDialog()238239 return (240 <button241 type="button"242 className={cn(alertDialogActionVariants(), className)}243 onClick={(e) => {244 onClick?.(e)245 onOpenChange(false)246 }}247 {...props}248 >249 {children}250 </button>251 )252}253254type AlertDialogCancelProps = {255 children: ReactNode256 className?: string257} & ButtonHTMLAttributes<HTMLButtonElement>258259export function AlertDialogCancel({ children, className, onClick, ...props }: AlertDialogCancelProps) {260 const { onOpenChange } = useAlertDialog()261262 return (263 <button264 type="button"265 className={cn(alertDialogCancelVariants(), className)}266 onClick={(e) => {267 onClick?.(e)268 onOpenChange(false)269 }}270 {...props}271 >272 {children}273 </button>274 )275}276Usages
Different variants and use cases for the Alert Dialog component.
Default
A basic alert dialog with title, description and action buttons.
Default.tsx
<AlertDialog>
<AlertDialogTrigger>Open</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your account.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>Controlled
An alert dialog with controlled open state.
Controlled.tsx
const [open, setOpen] = useState(false)
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger>Delete Account</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Account</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete your account? All of your data will be permanently removed.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={() => console.log("Deleted")}>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>