Naar inhoud

Knop

Een klikbare knop voor het verzenden van formulieren, werkbalken en bevestigingsvensters die iets onomkeerbaars doen — met dikke randen en een schaduw waarin hij bij het klikken wegzakt.

import { ArrowUpIcon } from "lucide-react"

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

De knop zet een actie in gang — verzenden, bevestigen, verwijderen. Gebouwd op de Base UI Button-component, vormgegeven volgens het neobrutalistische recept: dikke randen, een harde offset-schaduw en een indrukeffect waarbij de knop bij het klikken in zijn schaduw wegzakt.

Gebruik deze component bij:

  • Formulieren verzenden en primaire CTA’sdefault draagt de hoofdactie; secondary en outline voor alles eromheen.
  • Werkbalken en pictogramacties — de icon-maten maken vierkante knoppen; ButtonGroup zet ze op een rij als gesegmenteerde groepen.
  • Destructieve bevestigingendestructive voor verwijderen en weghalen, meestal achter een dialoogvenster.

Installatie

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

Installeer de volgende afhankelijkheden:

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

Kopieer en plak de volgende code in je project.

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 }

Werk de import-paden bij zodat ze overeenkomen met je projectstructuur.

Gebruik

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

Cursor

Tailwind v4 is voor de knop-component overgestapt van cursor: pointer naar cursor: default.

Als je het gedrag cursor: pointer wilt behouden, voeg dan de volgende code toe aan je CSS-bestand:

Je kunt dit ook inschakelen tijdens het opzetten van je project met npx shadcn@latest init --pointer.

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

Voorbeelden

Grootte

Gebruik de prop size om de grootte van de knop te wijzigen.

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"

Met pictogram

Vergeet niet om het attribuut data-icon="inline-start" of data-icon="inline-end" aan het pictogram toe te voegen voor de juiste ruimte.

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

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

Afgerond

Gebruik de klasse rounded-full om de knop af te ronden.

import { ArrowUpIcon } from "lucide-react"

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

Spinner

Render een <Spinner />-component binnen de knop om een laadstatus te tonen. Vergeet niet om het attribuut data-icon="inline-start" of data-icon="inline-end" aan de spinner toe te voegen voor de juiste ruimte.

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

Button Group

Gebruik de component ButtonGroup om een knopgroep te maken. Zie de documentatie Button Group voor meer details.

"use client"

import * as React from "react"

Je kunt de helper buttonVariants gebruiken om een link eruit te laten zien als een knop.

Gebruik <Button render={<a />} nativeButton={false} /> niet voor links. De component Button van Base UI past altijd role="button" toe, wat de semantische link-rol op <a>-elementen overschrijft. Gebruik in plaats daarvan buttonVariants met een gewone <a>-tag.

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

export function ButtonRender() {

RTL

Om RTL-ondersteuning in Neobrutalism in te schakelen, zie de RTL-configuratiegids.

"use client"

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

Toegankelijkheid

De knop volgt het WAI-ARIA Button-patroon. Hij rendert standaard een native <button>; wisselt render er een ander element in, dan koppelt Base UI role="button", tabindex en toetsenbordactivatie aan, zodat de vervanger zich voor hulptechnologie als een echte knop gedraagt (zie de opmerking bij Als link hierboven voor het ene geval waarin die rol verkeerd uitpakt).

Toetsenbordinteracties:

ToetsActie
Tab / Shift + TabVerplaatst de focus naar / weg van de knop
Space / EnterActiveert de knop

API-referentie

Button

De component Button is een wrapper rond het button-element die een verscheidenheid aan stijlen en functionaliteit toevoegt.

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