Bỏ qua đến nội dung

Nút

Một nút kích hoạt có thể nhấp để gửi biểu mẫu, thanh công cụ và các hộp xác nhận hủy bỏ — với viền dày và hiệu ứng bóng mờ xuất hiện khi nhấp vào.

import { ArrowUpIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

Button kích hoạt một hành động — gửi, xác nhận, xóa. Được xây dựng trên thành phần Base UI Button và tạo kiểu theo công thức neobrutalist: viền dày, một bóng đổ lệch đậm và hiệu ứng nhấn đẩy nút chìm vào chính bóng đó.

Nên dùng khi:

  • Gửi form và CTA chínhdefault mang hành động chính; secondaryoutline cho mọi thứ đứng cạnh nó.
  • Thanh công cụ và hành động dạng icon — các cỡ icon tạo nút vuông; ButtonGroup xếp chúng thành từng hàng phân đoạn.
  • Xác nhận hành động phá hủydestructive cho xóa và loại bỏ, thường nằm sau một dialog.

Cài đặt

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/button.json
npx shadcn@latest add https://neobrutalism.com/r/base/button.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/button.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/button.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/button.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
  cn(
    "group/button font-head font-medium inline-flex cursor-pointer items-center justify-center gap-2 rounded whitespace-nowrap select-none transition-all duration-200",
    "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-60",
    "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary aria-invalid:border-destructive",
    // Icons keep their own size; we only set a default when none is given so
    // Neobrutalism's h-4/size-4 icons aren't overridden.
    "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
  ),
  {
    variants: {
      variant: {
        default:
          "border-2 border-black bg-primary text-primary-foreground shadow-md transition duration-200 hover:-translate-x-0.5 hover:-translate-y-0.5 hover:bg-primary-hover hover:shadow-lg active:translate-x-1 active:translate-y-1 active:shadow-none",
        secondary:
          "border-2 border-black bg-secondary text-secondary-foreground shadow-md transition duration-200 hover:-translate-x-0.5 hover:-translate-y-0.5 hover:bg-secondary-hover hover:shadow-lg active:translate-x-1 active:translate-y-1 active:shadow-none",
        destructive:
          "border-2 border-black bg-destructive text-destructive-foreground shadow-md transition duration-200 hover:-translate-x-0.5 hover:-translate-y-0.5 hover:bg-destructive/90 hover:shadow-lg active:translate-x-1 active:translate-y-1 active:shadow-none",
        outline:
          "border-2 bg-transparent shadow-md transition duration-200 hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-lg active:translate-x-1 active:translate-y-1 active:shadow-none",
        ghost: "bg-transparent hover:bg-accent",
        link: "bg-transparent hover:underline",
      },
      size: {
        default: "px-4 py-1.5 text-base",
        xs: "px-2 py-0.5 text-xs",
        sm: "px-3 py-1 text-sm",
        lg: "px-6 py-2 text-base lg:px-8 lg:py-3 lg:text-lg",
        icon: "p-2",
        "icon-xs": "p-1",
        "icon-sm": "p-1.5",
        "icon-lg": "p-3",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

function Button({
  className,
  variant = "default",
  size = "default",
  asChild = false,
  ...props
}: React.ComponentProps<"button"> &
  VariantProps<typeof buttonVariants> & {
    asChild?: boolean
  }) {
  const Comp = asChild ? Slot.Root : "button"

  return (
    <Comp
      data-slot="button"
      data-variant={variant}
      data-size={size}
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  )
}

export { Button, buttonVariants }

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 { Button } from "@/components/ui/button"
<Button variant="outline">Button</Button>

Cursor

Tailwind v4 đã chuyển từ cursor: pointer sang cursor: default cho thành phần nút.

Nếu bạn muốn giữ hành vi cursor: pointer, hãy thêm đoạn mã sau vào file CSS của bạn:

Bạn cũng có thể bật điều này khi thiết lập dự án với npx shadcn@latest init --pointer.

globals.css
@layer base {
  button:not(:disabled),
  [role="button"]:not(:disabled) {
    cursor: pointer;
  }
}

Ví dụ

Kích thước

Dùng prop size để thay đổi kích thước của nút.

import { ArrowUpRightIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

Default

import { Button } from "@/components/ui/button"

export function ButtonDefault() {

Outline

import { Button } from "@/components/ui/button"

export function ButtonOutline() {

Secondary

import { Button } from "@/components/ui/button"

export function ButtonSecondary() {

Ghost

import { Button } from "@/components/ui/button"

export function ButtonGhost() {

Destructive

import { Button } from "@/components/ui/button"

export function ButtonDestructive() {
import { Button } from "@/components/ui/button"

export function ButtonLink() {

Icon

import { CircleFadingArrowUpIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

Với biểu tượng

Nhớ thêm thuộc tính data-icon="inline-start" hoặc data-icon="inline-end" vào biểu tượng để có khoảng cách đúng.

import { IconGitBranch, IconGitFork } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

Bo tròn

Dùng lớp rounded-full để bo tròn nút.

import { ArrowUpIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

Spinner

Hiển thị một thành phần <Spinner /> bên trong nút để thể hiện trạng thái đang tải. Nhớ thêm thuộc tính data-icon="inline-start" hoặc data-icon="inline-end" vào spinner để có khoảng cách đúng.

import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"

Button Group

Để tạo một nhóm nút, dùng thành phần ButtonGroup. Xem tài liệu Button Group để biết thêm chi tiết.

"use client"

import * as React from "react"

Bạn có thể dùng helper buttonVariants để làm cho một link trông giống một nút.

Đừng dùng <Button render={<a />} nativeButton={false} /> cho link. Thành phần Button của Base UI luôn áp dụng role="button", điều này ghi đè vai trò link ngữ nghĩa trên các phần tử <a>. Thay vào đó, hãy dùng buttonVariants với một thẻ <a> thông thường.

import { buttonVariants } from "@/components/ui/button"

export function ButtonRender() {

RTL

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

"use client"

import { ArrowRightIcon, PlusIcon } from "lucide-react"

Khả năng truy cập

Button tuân theo mẫu Button của WAI-ARIA. Theo mặc định nó render một <button> native; khi render thay vào một phần tử khác, Base UI tự nối role="button", tabindex và cách kích hoạt bằng bàn phím để phần tử thay thế hoạt động như một nút thật đối với công nghệ hỗ trợ (xem ghi chú Dưới dạng Link ở trên để biết trường hợp duy nhất mà role đó là sai).

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

PhímHành động
Tab / Shift + TabDi chuyển focus vào / ra khỏi nút
Space / EnterKích hoạt nút

Tham chiếu API

Button

Thành phần Button là một lớp bọc quanh phần tử button, thêm nhiều kiểu dáng và tính năng.

PropTypeDefault
variant"default" | "outline" | "ghost" | "destructive" | "secondary" | "link""default"
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"