Docs/Components/Alert Dialog

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 AlertDialog

Install 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"
2
3import 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"
7
8const 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)
11
12const 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)
15
16const alertDialogHeaderVariants = cva(
17 "flex flex-col space-y-2 text-center sm:text-left"
18)
19
20const alertDialogFooterVariants = cva(
21 "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2"
22)
23
24const alertDialogTitleVariants = cva(
25 "text-lg font-semibold"
26)
27
28const alertDialogDescriptionVariants = cva(
29 "text-sm text-muted-foreground"
30)
31
32const 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)
35
36const 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)
39
40type AlertDialogContextType = {
41 open: boolean
42 onOpenChange: (open: boolean) => void
43}
44
45const AlertDialogContext = createContext<AlertDialogContextType | undefined>(undefined)
46
47function useAlertDialog() {
48 const context = useContext(AlertDialogContext)
49 if (!context) {
50 throw new Error("AlertDialog components must be used within an AlertDialog")
51 }
52 return context
53}
54
55type AlertDialogProps = {
56 children: ReactNode
57 open?: boolean
58 defaultOpen?: boolean
59 onOpenChange?: (open: boolean) => void
60}
61
62export function AlertDialog({
63 children,
64 open: controlledOpen,
65 defaultOpen = false,
66 onOpenChange,
67}: AlertDialogProps) {
68 const [internalOpen, setInternalOpen] = useState(defaultOpen)
69
70 const isControlled = controlledOpen !== undefined
71 const open = isControlled ? controlledOpen : internalOpen
72
73 const handleOpenChange = useCallback((newOpen: boolean) => {
74 if (!isControlled) {
75 setInternalOpen(newOpen)
76 }
77 onOpenChange?.(newOpen)
78 }, [isControlled, onOpenChange])
79
80 return (
81 <AlertDialogContext.Provider value={{ open, onOpenChange: handleOpenChange }}>
82 {children}
83 </AlertDialogContext.Provider>
84 )
85}
86
87type AlertDialogTriggerProps = {
88 children: ReactNode
89 className?: string
90} & ButtonHTMLAttributes<HTMLButtonElement>
91
92export function AlertDialogTrigger({ children, className, ...props }: AlertDialogTriggerProps) {
93 const { onOpenChange } = useAlertDialog()
94
95 return (
96 <button
97 type="button"
98 className={className}
99 onClick={() => onOpenChange(true)}
100 {...props}
101 >
102 {children}
103 </button>
104 )
105}
106
107type AlertDialogPortalProps = {
108 children: ReactNode
109}
110
111export function AlertDialogPortal({ children }: AlertDialogPortalProps) {
112 const { open } = useAlertDialog()
113
114 if (!open) return null
115
116 return <>{children}</>
117}
118
119type AlertDialogOverlayProps = {
120 className?: string
121} & HTMLAttributes<HTMLDivElement>
122
123export function AlertDialogOverlay({ className, ...props }: AlertDialogOverlayProps) {
124 const { open, onOpenChange } = useAlertDialog()
125
126 return (
127 <div
128 className={cn(alertDialogOverlayVariants(), className)}
129 data-state={open ? "open" : "closed"}
130 onClick={() => onOpenChange(false)}
131 {...props}
132 />
133 )
134}
135
136type AlertDialogContentProps = {
137 children: ReactNode
138 className?: string
139} & HTMLAttributes<HTMLDivElement>
140
141export function AlertDialogContent({ children, className, ...props }: AlertDialogContentProps) {
142 const { open, onOpenChange } = useAlertDialog()
143
144 // Handle escape key
145 useEffect(() => {
146 const handleEscape = (e: KeyboardEvent) => {
147 if (e.key === "Escape") {
148 onOpenChange(false)
149 }
150 }
151
152 if (open) {
153 document.addEventListener("keydown", handleEscape)
154 document.body.style.overflow = "hidden"
155 }
156
157 return () => {
158 document.removeEventListener("keydown", handleEscape)
159 document.body.style.overflow = ""
160 }
161 }, [open, onOpenChange])
162
163 return (
164 <AlertDialogPortal>
165 <AlertDialogOverlay />
166 <div
167 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}
178
179type AlertDialogHeaderProps = {
180 children: ReactNode
181 className?: string
182} & HTMLAttributes<HTMLDivElement>
183
184export function AlertDialogHeader({ children, className, ...props }: AlertDialogHeaderProps) {
185 return (
186 <div className={cn(alertDialogHeaderVariants(), className)} {...props}>
187 {children}
188 </div>
189 )
190}
191
192type AlertDialogFooterProps = {
193 children: ReactNode
194 className?: string
195} & HTMLAttributes<HTMLDivElement>
196
197export function AlertDialogFooter({ children, className, ...props }: AlertDialogFooterProps) {
198 return (
199 <div className={cn(alertDialogFooterVariants(), className)} {...props}>
200 {children}
201 </div>
202 )
203}
204
205type AlertDialogTitleProps = {
206 children: ReactNode
207 className?: string
208} & HTMLAttributes<HTMLHeadingElement>
209
210export function AlertDialogTitle({ children, className, ...props }: AlertDialogTitleProps) {
211 return (
212 <h2 className={cn(alertDialogTitleVariants(), className)} {...props}>
213 {children}
214 </h2>
215 )
216}
217
218type AlertDialogDescriptionProps = {
219 children: ReactNode
220 className?: string
221} & HTMLAttributes<HTMLParagraphElement>
222
223export function AlertDialogDescription({ children, className, ...props }: AlertDialogDescriptionProps) {
224 return (
225 <p className={cn(alertDialogDescriptionVariants(), className)} {...props}>
226 {children}
227 </p>
228 )
229}
230
231type AlertDialogActionProps = {
232 children: ReactNode
233 className?: string
234} & ButtonHTMLAttributes<HTMLButtonElement>
235
236export function AlertDialogAction({ children, className, onClick, ...props }: AlertDialogActionProps) {
237 const { onOpenChange } = useAlertDialog()
238
239 return (
240 <button
241 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}
253
254type AlertDialogCancelProps = {
255 children: ReactNode
256 className?: string
257} & ButtonHTMLAttributes<HTMLButtonElement>
258
259export function AlertDialogCancel({ children, className, onClick, ...props }: AlertDialogCancelProps) {
260 const { onOpenChange } = useAlertDialog()
261
262 return (
263 <button
264 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}
276

Usages

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>