Bỏ qua đến nội dung

Sonner

Các thông báo dạng “toast” xếp chồng lên nhau cho các thao tác xác nhận lưu, tải lên và cửa sổ hoàn tác — với hiệu ứng viền và bóng theo phong cách “neobrutalist”.

"use client"

import { toast } from "sonner"

Sonner hiển thị các toast xếp chồng, tự động biến mất chỉ với một lời gọi toast() — mount một <Toaster /> rồi bắn thông báo từ bất cứ đâu, không cần nối context. Đây không phải primitive của Radix hay Base UI: cả hai biến thể backend đều bọc cùng thư viện Sonner của emilkowalski, 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:

  • Phản hồi hành động — “Đã lưu”, “Đã sao chép vào clipboard”, “Đã cập nhật cài đặt” sau khi một mutation hoàn tất.
  • Thao tác bất đồng bộtoast.promise() theo dõi một lượt tải lên hoặc deploy từ đang tải đến thành công hay lỗi trong cùng một toast.
  • Khoảng thời gian hoàn tác — xóa và lưu trữ kèm nút hành động, để các thao tác phá hủy có thời gian ân hạn.

Giới thiệu

Sonner được phát triển và duy trì bởi emilkowalski.

Cài đặt

Chạy lệnh sau:

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

Thêm component Toaster.

app/layout.tsx
import { Toaster } from "@/components/ui/sonner"
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head />
      <body>
        <main>{children}</main>
        <Toaster />
      </body>
    </html>
  )
}

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

pnpm add sonner next-themes
npm install sonner next-themes
yarn add sonner next-themes
bun add sonner next-themes

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

components/ui/sonner.tsx
"use client"

import {
  CircleCheckIcon,
  InfoIcon,
  Loader2Icon,
  OctagonXIcon,
  TriangleAlertIcon,
} from "lucide-react"
import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"

const Toaster = ({ ...props }: ToasterProps) => {
  const { theme = "system" } = useTheme()

  return (
    <Sonner
      theme={theme as ToasterProps["theme"]}
      className="toaster group"
      icons={{
        success: <CircleCheckIcon className="size-4" />,
        info: <InfoIcon className="size-4" />,
        warning: <TriangleAlertIcon className="size-4" />,
        error: <OctagonXIcon className="size-4" />,
        loading: <Loader2Icon className="size-4 animate-spin" />,
      }}
      toastOptions={{
        // Fully unstyled so the neobrutalist recipe below owns every surface:
        // hard offset shadow, 2px border, square corners, press-down buttons.
        unstyled: true,
        classNames: {
          toast:
            "group/toast relative flex w-(--width) items-center gap-3 rounded border-2 border-border bg-popover p-4 font-sans text-popover-foreground shadow-md",
          content: "flex min-w-0 flex-col gap-0.5",
          title: "font-head text-sm font-medium",
          description: "text-sm text-muted-foreground",
          icon: "shrink-0",
          actionButton:
            "ms-auto h-fit min-w-fit shrink-0 rounded border-2 border-border bg-primary px-2 py-1 text-xs font-medium text-primary-foreground shadow-sm transition-all duration-200 hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none",
          cancelButton:
            "ms-auto h-fit min-w-fit shrink-0 rounded border-2 border-border bg-muted px-2 py-1 text-xs font-medium text-foreground shadow-sm transition-all duration-200 hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none",
          closeButton:
            "absolute -top-2 -start-2 rounded-full border-2 border-border bg-background p-0.5 transition-colors hover:bg-muted",
          success: "[&_[data-icon]]:text-chart-2",
          warning: "[&_[data-icon]]:text-chart-1",
          error: "[&_[data-icon]]:text-destructive",
        },
      }}
      {...props}
    />
  )
}

export { Toaster }

Thêm component Toaster.

app/layout.tsx
import { Toaster } from "@/components/ui/sonner"
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head />
      <body>
        <Toaster />
        <main>{children}</main>
      </body>
    </html>
  )
}

Cách dùng

import { toast } from "sonner"
toast("Event has been created.")

Ví dụ

Loại

"use client"

import { toast } from "sonner"

Mô tả

"use client"

import { toast } from "sonner"

Vị trí

Dùng prop position để thay đổi vị trí của toast.

"use client"

import { toast } from "sonner"

Khả năng truy cập

Không có mẫu WAI-ARIA APG chuyên biệt cho toast; hướng dẫn gần nhất là mẫu Alert. Sonner render toast vào một ARIA live region ở mức polite, nên trình đọc màn hình đọc chúng lên mà không cướp focus. Vùng này nằm ngoài thứ tự Tab thông thường — chỉ tiếp cận được qua phím tắt — và bộ đếm thời gian tự đóng sẽ tạm dừng khi chồng toast được di chuột lên, được focus, hoặc khi tab bị ẩn.

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

PhímHành động
Alt + TFocus và mở rộng chồng toast (tùy chỉnh qua prop hotkey trên Toaster)
Tab / Shift + TabDi chuyển qua các toast cùng nút hành động / nút đóng của chúng
Enter / SpaceKích hoạt nút hành động hoặc nút đóng đang được focus
EscapeThu gọn chồng toast khi focus đang ở bên trong

Tham chiếu API

Xem Tham chiếu API Sonner để biết thêm thông tin.