Bỏ qua đến nội dung

Hộp thoại cảnh báo

Một cửa sổ bật lên ngăn chặn quá trình thực hiện các hành động gây mất dữ liệu — xóa, đăng xuất, hủy bỏ — với phong cách thiết kế theo trường phái tân thô mộc, kết hợp giữa đường viền và bóng đổ.

import {
  AlertDialog,
  AlertDialogAction,

Alert dialog chặn người dùng lại bằng một modal buộc phải đưa ra lựa chọn rõ ràng — nhấp ra ngoài sẽ không đóng nó. Được xây dựng trên thành phần Base UI Alert Dialog và tạo kiểu theo công thức neobrutalist: viền dày, bóng đổ đậm và kiểu chữ đậm.

Nên dùng khi:

  • Xác nhận hành động phá hủy — xóa tài khoản, loại một thành viên khỏi nhóm, dọn trắng một dự án.
  • Thao tác không thể hoàn tác — đăng, gửi, nộp; bất cứ việc gì không có đường lùi.
  • Chặn khi có thay đổi chưa lưu — cảnh báo trước khi rời khỏi một form còn dở.

Cài đặt

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/alert-dialog.json
npx shadcn@latest add https://neobrutalism.com/r/base/alert-dialog.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/alert-dialog.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/alert-dialog.json

Cài đặt các dependency sau:

pnpm add @base-ui/react
npm install @base-ui/react
yarn add @base-ui/react
bun add @base-ui/react

Sao chép và dán đoạn mã sau vào dự án của bạn.

components/ui/alert-dialog.tsx
"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,
}

Cập nhật các đường dẫn import cho khớp với cấu trúc dự án của bạn.

Cách dùng

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>

Kết hợp

Dùng cách kết hợp sau để dựng một AlertDialog:

AlertDialog
├── AlertDialogTrigger
└── AlertDialogContent
    ├── AlertDialogHeader
    │   ├── AlertDialogMedia
    │   ├── AlertDialogTitle
    │   └── AlertDialogDescription
    └── AlertDialogFooter
        ├── AlertDialogCancel
        └── AlertDialogAction

Ví dụ

Cơ bản

Một hộp thoại cảnh báo cơ bản với tiêu đề, mô tả cùng các nút hủy và tiếp tục.

import {
  AlertDialog,
  AlertDialogAction,

Nhỏ

Dùng prop size="sm" để làm hộp thoại cảnh báo nhỏ hơn.

import {
  AlertDialog,
  AlertDialogAction,

Media

Dùng thành phần AlertDialogMedia để thêm một phần tử media, chẳng hạn biểu tượng hoặc hình ảnh, vào hộp thoại cảnh báo.

import { CircleFadingPlusIcon } from "lucide-react"

import {

Nhỏ với Media

Dùng prop size="sm" để làm hộp thoại cảnh báo nhỏ hơn và thành phần AlertDialogMedia để thêm một phần tử media, chẳng hạn biểu tượng hoặc hình ảnh.

import { BluetoothIcon } from "lucide-react"

import {

Phá hủy

Dùng thành phần AlertDialogAction để thêm một nút hành động phá hủy vào hộp thoại cảnh báo.

import { Trash2Icon } from "lucide-react"

import {

RTL

Để bật hỗ trợ RTL trong Neobrutalism, xem hướng dẫn cấu hình RTL.

"use client"

import { BluetoothIcon } from "lucide-react"

Khả năng truy cập

Alert dialog tuân theo mẫu Alert Dialog của WAI-ARIA: popup render role="alertdialog" được nối với tiêu đề và mô tả qua aria-labelledbyaria-describedby, và focus bị giữ lại bên trong suốt lúc dialog mở.

Tương tác bàn phím:

PhímHành động
Space / EnterMở dialog từ trigger đang được focus
Tab / Shift + TabLuân chuyển focus qua các phần tử focus được trong dialog
EscĐóng dialog và trả focus về trigger

Tham chiếu API

size

Dùng prop size trên thành phần AlertDialogContent để kiểm soát kích thước của hộp thoại cảnh báo. Nó nhận các giá trị sau:

PropTypeDefault
size"default" | "sm""default"

Để biết thêm thông tin về các thành phần khác và prop của chúng, xem tài liệu Base UI.