Zum Inhalt springen

Button

Ein anklickbarer Auslöser für Formularabsendungen, Symbolleisten und Bestätigungsfelder mit destruktiver Wirkung – mit dicken Rändern und einem Schatten, in den er beim Klicken einsinkt.

import { ArrowUpIcon } from "lucide-react"

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

Der Button löst eine Aktion aus – absenden, bestätigen, löschen. Er basiert auf der Base UI Button-Komponente und folgt dem neobrutalistischen Rezept: dicke Rahmen, ein harter versetzter Schatten und ein aktiver Druckzustand, der den Button in den Schatten hineindrückt.

Die richtige Wahl für:

  • Formularabsendungen und primäre CTAsdefault trägt die Hauptaktion; secondary und outline für alles daneben.
  • Toolbars und Icon-Aktionen – die icon-Größen ergeben quadratische Buttons; ButtonGroup reiht sie zu segmentierten Zeilen auf.
  • Destruktive Bestätigungendestructive für Löschen und Entfernen, meist hinter einem Dialog.

Installation

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

Installiere die folgenden Abhängigkeiten:

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

Kopiere den folgenden Code und füge ihn in dein Projekt ein.

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 }

Passe die Import-Pfade an die Struktur deines Projekts an.

Verwendung

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

Cursor

Tailwind v4 ist für die Button-Komponente von cursor: pointer auf cursor: default umgestiegen.

Wenn du das Verhalten cursor: pointer beibehalten willst, füge den folgenden Code zu deiner CSS-Datei hinzu:

Du kannst dies auch beim Einrichten des Projekts mit npx shadcn@latest init --pointer aktivieren.

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

Beispiele

Größe

Verwende die Prop size, um die Größe des Buttons zu ändern.

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"

Mit Icon

Denk daran, dem Icon das Attribut data-icon="inline-start" oder data-icon="inline-end" für den korrekten Abstand hinzuzufügen.

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

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

Abgerundet

Verwende die Klasse rounded-full, um den Button abzurunden.

import { ArrowUpIcon } from "lucide-react"

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

Spinner

Rendere eine <Spinner />-Komponente innerhalb des Buttons, um einen Ladezustand anzuzeigen. Denk daran, dem Spinner das Attribut data-icon="inline-start" oder data-icon="inline-end" für den korrekten Abstand hinzuzufügen.

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

Button Group

Um eine Button-Gruppe zu erstellen, verwende die ButtonGroup-Komponente. Weitere Details findest du in der Button Group-Dokumentation.

"use client"

import * as React from "react"

Du kannst den Helfer buttonVariants verwenden, damit ein Link wie ein Button aussieht.

Verwende <Button render={<a />} nativeButton={false} /> nicht für Links. Die Button-Komponente von Base UI wendet immer role="button" an, was die semantische Link-Rolle bei <a>-Elementen überschreibt. Verwende stattdessen buttonVariants mit einem einfachen <a>-Tag.

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

export function ButtonRender() {

RTL

Um die RTL-Unterstützung in Neobrutalism zu aktivieren, siehe die RTL-Konfigurationsanleitung.

"use client"

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

Barrierefreiheit

Der Button folgt dem WAI-ARIA-Button-Pattern. Standardmäßig rendert er einen nativen <button>; tauscht render ein anderes Element ein, verdrahtet Base UI role="button", tabindex und die Tastaturaktivierung, sodass sich der Ersatz für assistive Technologien wie ein echter Button verhält (siehe den Hinweis unter „Als Link“ oben für den einen Fall, in dem diese Rolle falsch ist).

Tastaturinteraktionen:

TasteAktion
Tab / Shift + TabBewegt den Fokus zum Button hin / vom Button weg
Space / EnterAktiviert den Button

API-Referenz

Button

Die Button-Komponente ist ein Wrapper um das button-Element, der eine Vielzahl von Stilen und Funktionen hinzufügt.

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