콘텐츠로 건너뛰기

버튼

양식 제출, 툴바, 취소 확인 버튼 등을 위한 클릭 가능한 트리거로, 두꺼운 테두리가 있으며 클릭 시 테두리 안쪽으로 그림자가 스며드는 효과가 있습니다.

import { ArrowUpIcon } from "lucide-react"

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

버튼은 제출, 확인, 삭제 같은 동작을 실행합니다. Base UI Button 컴포넌트를 기반으로 하며, 네오브루탈리즘 공식에 맞춰 두꺼운 테두리와 선명하게 오프셋된 그림자를 두르고, 누르면 그림자 쪽으로 눌려 들어가는 액티브 효과를 더했습니다.

다음과 같은 경우에 사용합니다.

  • 폼 제출과 주요 CTA — 핵심 동작은 default가 맡고, 그 옆에 놓이는 것들은 secondaryoutline으로 처리합니다.
  • 툴바와 아이콘 동작icon 크기로 정사각형 버튼을 만들고, ButtonGroup으로 세그먼트 형태의 한 줄에 정렬합니다.
  • 파괴적인 작업 확인 — 삭제와 제거에는 destructive를 쓰고, 보통 다이얼로그 뒤에 둡니다.

설치

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

다음 의존성을 설치합니다:

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

다음 코드를 복사해 프로젝트에 붙여넣습니다.

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 }

프로젝트 설정에 맞게 import 경로를 업데이트합니다.

사용법

import { Button } from "@/components/ui/button"
<Button variant="outline">Button</Button>

커서

Tailwind v4는 버튼 컴포넌트의 cursor: pointercursor: default변경했습니다.

cursor: pointer 동작을 유지하고 싶다면 다음 코드를 CSS 파일에 추가하세요:

프로젝트 설정 시 npx shadcn@latest init --pointer로 활성화할 수도 있습니다.

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

예제

크기

size prop을 사용해 버튼의 크기를 변경합니다.

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"

아이콘 포함

올바른 간격을 위해 아이콘에 data-icon="inline-start" 또는 data-icon="inline-end" 속성을 추가하는 것을 잊지 마세요.

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

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

둥근 모서리

rounded-full 클래스를 사용해 버튼을 둥글게 만듭니다.

import { ArrowUpIcon } from "lucide-react"

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

Spinner

버튼 안에 <Spinner /> 컴포넌트를 렌더링하여 로딩 상태를 표시합니다. 올바른 간격을 위해 스피너에 data-icon="inline-start" 또는 data-icon="inline-end" 속성을 추가하는 것을 잊지 마세요.

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

Button Group

버튼 그룹을 만들려면 ButtonGroup 컴포넌트를 사용합니다. 자세한 내용은 Button Group 문서를 참조하세요.

"use client"

import * as React from "react"

링크로 사용

buttonVariants 헬퍼를 사용해 링크를 버튼처럼 보이게 만들 수 있습니다.

링크에는 <Button render={<a />} nativeButton={false} />를 사용하지 마세요. Base UI의 Button 컴포넌트는 항상 role="button"을 적용하므로, <a> 요소의 시맨틱 링크 role을 덮어씁니다. 대신 일반 <a> 태그와 함께 buttonVariants를 사용하세요.

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

export function ButtonRender() {

RTL

Neobrutalism에서 RTL을 활성화하는 방법은 RTL 설정 가이드를 참조하세요.

"use client"

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

접근성

버튼은 WAI-ARIA Button 패턴을 따릅니다. 기본적으로 네이티브 <button>을 렌더링하고, render로 다른 요소를 끼워 넣으면 Base UI가 role="button"tabindex, 키보드 활성화를 연결해 주므로 보조 기술이 보기에도 대체 요소가 진짜 버튼처럼 동작합니다(이 role이 맞지 않는 유일한 경우는 위의 링크로 사용 설명을 참고하세요).

키보드 상호작용:

동작
Tab / Shift + Tab버튼으로 포커스 이동, 또는 버튼에서 포커스 이탈
Space / Enter버튼 활성화

API 참조

Button

Button 컴포넌트는 button 요소를 감싸 다양한 스타일과 기능을 추가합니다.

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