Hộp thoại
Một cửa sổ bật lên dùng để xác nhận, điền biểu mẫu nhanh và hiển thị thông báo cảnh báo trước các thao tác có thể gây mất dữ liệu — hiển thị chồng lên trang với viền dày và bóng đổ đậm.
import { Button } from "@/components/ui/button"
import {
Dialog,Dialog mở một cửa sổ modal nằm trên trang — focus chuyển vào bên trong, phần nội dung phía sau bị vô hiệu hóa, và khi đóng lại bạn quay về đúng chỗ cũ. Được xây dựng trên primitive Base UI 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 — xóa, đăng xuất, hạ cấp gói; bất cứ thao tác phá hủy nào cũng đáng được chặn lại một nhịp.
- Biểu mẫu nhanh — sửa hồ sơ, đổi tên dự án, mời đồng đội mà không phải rời khỏi trang.
- Xem chi tiết tập trung — xem trước ảnh, nội dung giấy phép, ghi chú phát hành; những thứ không cần một route riêng.
Cài đặt#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/dialog.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/dialog.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/dialog.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/dialog.json
Cài đặt các phụ thuộc sau:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
Sao chép và dán đoạn mã sau vào dự án của bạn.
"use client"
import * as React from "react"
import { XIcon } from "lucide-react"
import { Dialog as DialogPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 isolate 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 DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded border-2 bg-popover p-4 text-sm text-popover-foreground shadow-md duration-100 outline-none 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}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close data-slot="dialog-close" asChild>
<Button
variant="ghost"
className="absolute top-2 right-2"
size="icon-sm"
>
<XIcon />
<span className="sr-only">Close</span>
</Button>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="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 sm:flex-row sm:justify-end",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
</DialogPrimitive.Close>
)}
</div>
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn(
"font-head text-base leading-none font-medium",
className
)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn(
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}
Cập nhật các đường dẫn import cho phù hợp với thiết lập dự án của bạn.
Cách dùng#
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"<Dialog>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your account
and remove your data from our servers.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>Kết hợp#
Dùng cách kết hợp sau để xây dựng một Dialog:
Dialog
├── DialogTrigger
└── DialogContent
├── DialogHeader
│ ├── DialogTitle
│ └── DialogDescription
└── DialogFooterVí dụ#
Nút đóng tùy chỉnh#
Thay thế điều khiển đóng mặc định bằng nút của riêng bạn.
import { Button } from "@/components/ui/button"
import {
Dialog,Không có nút đóng#
Dùng showCloseButton={false} để ẩn nút đóng.
import { Button } from "@/components/ui/button"
import {
Dialog,Chân trang cố định#
Giữ các hành động hiển thị trong khi nội dung cuộn.
import { Button } from "@/components/ui/button"
import {
Dialog,Nội dung có thể cuộn#
Nội dung dài có thể cuộn trong khi tiêu đề vẫn hiển thị.
import { Button } from "@/components/ui/button"
import {
Dialog,RTL#
Để bật hỗ trợ RTL trong Neobrutalism, hãy xem hướng dẫn cấu hình RTL.
"use client"
import {Khả năng truy cập#
Dialog tuân theo mẫu Dialog (Modal) của WAI-ARIA: phần nội dung render với role="dialog" cùng aria-modal, được gắn nhãn bởi DialogTitle và mô tả bởi DialogDescription, focus bị giữ lại bên trong khi dialog đang mở, và khi đóng thì focus trở về trigger.
Tương tác bàn phím:
| Phím | Hành động |
|---|---|
Space / Enter | Mở dialog từ trigger đang được focus |
Tab / Shift + Tab | Chuyển focus đến phần tử kế tiếp / trước đó bên trong dialog (focus bị giữ lại) |
Esc | Đóng dialog và trả focus về trigger |
Tham chiếu API#
Xem tài liệu Base UI để biết thêm thông tin.