import {
AlertDialog,
AlertDialogAction,얼럿 다이얼로그는 명시적인 선택을 요구하는 모달로 사용자의 흐름을 끊습니다. 바깥쪽을 클릭해도 닫히지 않습니다. Base UI Alert Dialog 컴포넌트를 기반으로 하며, 네오브루탈리즘 공식(두꺼운 테두리, 선명한 그림자, 굵은 서체)에 맞춰 스타일을 입혔습니다.
다음과 같은 경우에 사용합니다.
- 파괴적인 작업 확인 — 계정 삭제, 팀원 제거, 프로젝트 전체 삭제.
- 되돌릴 수 없는 작업 — 게시, 전송, 제출처럼 취소할 방법이 없는 모든 동작.
- 저장하지 않은 변경 사항 경고 — 수정 중인 폼을 벗어나기 전에 알려 줍니다.
설치#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/alert-dialog.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/alert-dialog.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/alert-dialog.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/alert-dialog.json
다음 의존성을 설치합니다:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
다음 코드를 복사해 프로젝트에 붙여넣습니다.
"use client"
import * as React from "react"
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
}
function AlertDialogTrigger({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
)
}
function AlertDialogPortal({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
)
}
function AlertDialogOverlay({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return (
<AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn(
"fixed inset-0 z-50 bg-foreground/20 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className
)}
{...props}
/>
)
}
function AlertDialogContent({
className,
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
size?: "default" | "sm"
}) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
data-slot="alert-dialog-content"
data-size={size}
className={cn(
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded border-2 bg-popover p-4 text-popover-foreground shadow-md duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className
)}
{...props}
/>
</AlertDialogPortal>
)
}
function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-header"
className={cn(
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
className
)}
{...props}
/>
)
}
function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
className={cn(
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t-2 bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
className
)}
{...props}
/>
)
}
function AlertDialogMedia({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-media"
className={cn(
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
className
)}
{...props}
/>
)
}
function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn(
"font-head text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
className
)}
{...props}
/>
)
}
function AlertDialogDescription({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn(
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
{...props}
/>
)
}
function AlertDialogAction({
className,
variant = "default",
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
return (
<Button variant={variant} size={size} asChild>
<AlertDialogPrimitive.Action
data-slot="alert-dialog-action"
className={cn(className)}
{...props}
/>
</Button>
)
}
function AlertDialogCancel({
className,
variant = "outline",
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
return (
<Button variant={variant} size={size} asChild>
<AlertDialogPrimitive.Cancel
data-slot="alert-dialog-cancel"
className={cn(className)}
{...props}
/>
</Button>
)
}
export {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
}
프로젝트 설정에 맞게 import 경로를 업데이트합니다.
사용법#
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"<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>구성#
AlertDialog를 만들려면 다음 구성을 사용합니다:
AlertDialog
├── AlertDialogTrigger
└── AlertDialogContent
├── AlertDialogHeader
│ ├── AlertDialogMedia
│ ├── AlertDialogTitle
│ └── AlertDialogDescription
└── AlertDialogFooter
├── AlertDialogCancel
└── AlertDialogAction예제#
기본#
제목, 설명, 취소 및 계속 버튼을 갖춘 기본적인 알림 다이얼로그입니다.
import {
AlertDialog,
AlertDialogAction,작은 크기#
size="sm" prop을 사용하면 알림 다이얼로그를 더 작게 만들 수 있습니다.
import {
AlertDialog,
AlertDialogAction,미디어#
AlertDialogMedia 컴포넌트를 사용하면 아이콘이나 이미지 같은 미디어 요소를 알림 다이얼로그에 추가할 수 있습니다.
import { CircleFadingPlusIcon } from "lucide-react"
import {미디어 포함 작은 크기#
size="sm" prop으로 알림 다이얼로그를 더 작게 만들고, AlertDialogMedia 컴포넌트로 아이콘이나 이미지 같은 미디어 요소를 추가합니다.
import { BluetoothIcon } from "lucide-react"
import {파괴적#
AlertDialogAction 컴포넌트를 사용해 알림 다이얼로그에 파괴적인 액션 버튼을 추가합니다.
import { Trash2Icon } from "lucide-react"
import {RTL#
Neobrutalism에서 RTL을 활성화하는 방법은 RTL 설정 가이드를 참조하세요.
"use client"
import { BluetoothIcon } from "lucide-react"접근성#
얼럿 다이얼로그는 WAI-ARIA Alert Dialog 패턴을 따릅니다. 팝업은 role="alertdialog"로 렌더링되며 aria-labelledby와 aria-describedby로 제목과 설명에 연결되고, 열려 있는 동안 포커스는 내부에 머무릅니다.
키보드 상호작용:
| 키 | 동작 |
|---|---|
Space / Enter | 포커스된 트리거에서 다이얼로그 열기 |
Tab / Shift + Tab | 다이얼로그 안의 포커스 가능한 요소 사이를 순환 |
Esc | 다이얼로그를 닫고 트리거로 포커스 복귀 |
API 참조#
size#
AlertDialogContent 컴포넌트의 size prop으로 알림 다이얼로그의 크기를 제어합니다. 다음 값을 받습니다:
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "default" |
다른 컴포넌트와 그 prop에 대한 자세한 내용은 Base UI 문서를 참조하세요.