Przejdź do treści

Button

Wywołuje akcję. Najważniejszy element interaktywny każdego interfejsu — i ten, którego najłatwiej nadużyć.

Kod źródłowyBase UIRejestr
import { ArrowRightIcon } from "lucide-react"

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

export default function ButtonDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button variant="outline">Anuluj</Button>
      <Button>
        Dalej
        <ArrowRightIcon data-icon="inline-end" />
      </Button>
    </div>
  )
}

Anatomia

  1. 1KontenerMa wypełnienie, obramowanie i pierścień fokusu zależne od wariantu. Jego wysokości pokrywają się z wysokościami pól tekstowych.
  2. 2Ikona na początkuOpcjonalna. Rozmiar i odstęp nadaje jej przycisk; po tej stronie padding się zmniejsza.
  3. 3EtykietaCzasownik, który nazywa efekt; wielka litera tylko na początku.
  4. 4Ikona na końcuOpcjonalna. Sygnalizuje kierunek albo menu.

Instalacja

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/button.json

Użycie

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

Przykłady

Warianty

Siedem wariantów wyraża hierarchię, nie dekorację. Główny przycisk prfct jest celowo monochromatyczny: kolor zarezerwowano dla znaczenia — zaznaczenia, fokusu, statusu — więc jedyna kolorowa akcja na powierzchni zawsze jest tą najważniejszą.

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

export default function ButtonVariants() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button>Główny</Button>
      <Button variant="brand">Marka</Button>
      <Button variant="secondary">Drugorzędny</Button>
      <Button variant="outline">Konturowy</Button>
      <Button variant="ghost">Przezroczysty</Button>
      <Button variant="destructive">Destrukcyjny</Button>
      <Button variant="link">Link</Button>
    </div>
  )
}
WariantZastosowanie
defaultJedyna główna akcja widoku: Zapisz, Utwórz projekt, Kontynuuj.
brandGłówna akcja, która ma nieść markę — zwykle na stronach marketingowych.
secondaryAkcje pomocnicze obok akcji głównej.
outlineNeutralne akcje z wyraźną krawędzią: Anuluj, Eksportuj, akcje na paskach narzędzi.
ghostMniej eksponowane akcje w gęstych interfejsach: na paskach narzędzi, w wierszach tabel, w nagłówkach kart.
destructiveAkcje nieodwracalne: Usuń, Unieważnij. Zwykle w oknie potwierdzenia.
linkNawigacja, która musi wyglądać jak tekst — w zdaniach albo w stopkach.

Rozmiary

Przyciski mają pięć wysokości: xs 28, sm 32, default 36, lg 40 i xl 48 pikseli. Pola tekstowe i listy wyboru mają rozmiary sm, default i lg o tych samych wysokościach, więc kontrolki w jednym rzędzie zawsze są wyrównane. Przyciski z samą ikoną są kwadratowe w każdym rozmiarze.

import { PlusIcon } from "lucide-react"

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

export default function ButtonSizes() {
  return (
    <div className="flex flex-col items-center gap-6">
      <div className="flex flex-wrap items-center justify-center gap-3">
        <Button size="xs">Bardzo mały</Button>
        <Button size="sm">Mały</Button>
        <Button>Domyślny</Button>
        <Button size="lg">Duży</Button>
        <Button size="xl">Bardzo duży</Button>
      </div>
      <div className="flex flex-wrap items-center justify-center gap-3">
        <Button size="icon-xs" variant="outline" aria-label="Dodaj element">
          <PlusIcon />
        </Button>
        <Button size="icon-sm" variant="outline" aria-label="Dodaj element">
          <PlusIcon />
        </Button>
        <Button size="icon" variant="outline" aria-label="Dodaj element">
          <PlusIcon />
        </Button>
        <Button size="icon-lg" variant="outline" aria-label="Dodaj element">
          <PlusIcon />
        </Button>
      </div>
    </div>
  )
}

Z ikonami

Oznacz ikony atrybutem data-icon="inline-start" lub data-icon="inline-end". Przycisk zmniejszy padding po tej stronie, żeby zachować równowagę optyczną, i sam dobierze rozmiar ikony — nigdy nie dodawaj klas rozmiaru do ikon wewnątrz przycisku.

import { DownloadIcon, ExternalLinkIcon, SendIcon } from "lucide-react"

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

export default function ButtonWithIcon() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button>
        <SendIcon data-icon="inline-start" />
        Wyślij zaproszenie
      </Button>
      <Button variant="outline">
        <DownloadIcon data-icon="inline-start" />
        Eksportuj CSV
      </Button>
      <Button variant="ghost">
        Otwórz w nowej karcie
        <ExternalLinkIcon data-icon="inline-end" />
      </Button>
    </div>
  )
}
<Button>
  <SendIcon data-icon="inline-start" />
  Send invite
</Button>

Sama ikona

Przycisk z samą ikoną nie ma widocznego tekstu, więc musi mieć aria-label. Dodaj do niego podpowiedź, która powtarza etykietę — z myślą o widzących użytkownikach myszy.

import { CopyIcon, PencilIcon, Trash2Icon } from "lucide-react"

import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip"

const actions = [
  { label: "Edytuj", icon: PencilIcon },
  { label: "Duplikuj", icon: CopyIcon },
  { label: "Usuń", icon: Trash2Icon },
]

export default function ButtonIconOnly() {
  return (
    <div className="flex items-center gap-1">
      {actions.map(({ label, icon: Icon }) => (
        <Tooltip key={label}>
          <TooltipTrigger
            render={<Button variant="ghost" size="icon" aria-label={label} />}
          >
            <Icon />
          </TooltipTrigger>
          <TooltipContent>{label}</TooltipContent>
        </Tooltip>
      ))}
    </div>
  )
}

Ładowanie

loading zastępuje etykietę wskaźnikiem ładowania bez zmiany szerokości przycisku, ustawia aria-busy i blokuje kliknięcia — ale zostawia fokus klawiatury na przycisku, więc użytkownicy czytników ekranu i klawiatury nie tracą orientacji.

"use client"

import * as React from "react"

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

export default function ButtonLoading() {
  const [saving, setSaving] = React.useState(false)

  function save() {
    setSaving(true)
    setTimeout(() => setSaving(false), 2000)
  }

  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button loading={saving} onClick={save}>
        Zapisz zmiany
      </Button>
      <Button variant="outline" loading>
        Publikowanie
      </Button>
    </div>
  )
}
Dlaczego prop, a nie kompozycja?
Ręczna zamiana etykiety na wskaźnik ładowania zwykle zwęża przycisk, a gdy przycisk zostaje wyłączony, fokus przepada. loading zachowuje jedno i drugie. Jeśli potrzebujesz innego układu, nadal możesz samodzielnie użyć komponentu Spinner.

Akcja, która przenosi w inne miejsce, jest linkiem, nawet jeśli wygląda jak przycisk: czytniki ekranu odczytują ją jako link, a użytkownicy mogą otworzyć ją w nowej karcie. Nadaj linkowi wygląd przycisku za pomocą buttonVariants, zamiast renderować Button jako link — Base UI nadałby mu role="button".

import { ArrowUpRightIcon } from "lucide-react"
import Link from "next/link"

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

export default function ButtonAsLink() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Link href="/pl/docs/installation" className={buttonVariants()}>
        Zacznij
      </Link>
      <a
        href="https://base-ui.com"
        target="_blank"
        rel="noreferrer"
        className={buttonVariants({ variant: "outline" })}
      >
        Dokumentacja Base UI
        <ArrowUpRightIcon data-icon="inline-end" />
      </a>
    </div>
  )
}
import Link from "next/link"
import { buttonVariants } from "@/components/ui/button"

<Link href="/docs/installation" className={buttonVariants()}>
  Get started
</Link>

Wytyczne

Kiedy używać

  • Do wywołania akcji na bieżącej stronie: wysłania formularza, otwarcia okna dialogowego, zapisu, usunięcia.
  • Do rozpoczęcia procesu: Utwórz projekt, Zaproś członka zespołu.

Kiedy nie używać

  • Do nawigacji. Użyj linku — ostylowanego za pomocą buttonVariants, jeśli ma wyglądać jak przycisk.
  • Do włączania i wyłączania ustawienia — użyj komponentu Switch.
  • Do wyboru jednej opcji z niewielkiego zestawu — użyj komponentu Toggle Group.

Hierarchia

Daj każdemu widokowi jedną akcję główną. Wszystkie pozostałe schodzą niżej — do outline, secondary lub ghost. Akcję główną umieść na końcu rzędu (w stopkach — wyrównaną do prawej), a akcje rezygnacji, takie jak Anuluj, na przeciwległym końcu.

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

export default function ButtonHierarchy() {
  return (
    <div className="flex w-full max-w-md items-center justify-between gap-3 rounded-xl border bg-card p-4 shadow-raised">
      <Button variant="ghost">Odrzuć</Button>
      <div className="flex items-center gap-2">
        <Button variant="outline">Zapisz szkic</Button>
        <Button>Opublikuj</Button>
      </div>
    </div>
  )
}
DobrzeJedna akcja główna, wsparta spokojniejszymi.
ŹleKilka głównych przycisków konkuruje ze sobą — żaden nie wygląda na najważniejszy.

Etykiety

Zacznij od czasownika i nazwij efekt: Zapisz zmiany, Usuń projekt, Wyślij 3 zaproszenia. Wielką literą pisz tylko pierwsze słowo, zmieść się w jednym–trzech słowach i nigdy nie kończ etykiety znakiem interpunkcyjnym. Etykieta musi mieć sens także wtedy, gdy czytnik ekranu odczyta ją bez kontekstu.

DobrzeKonkretne czasowniki mówią dokładnie, co się stanie.
ŹleOgólnikowe odpowiedzi zmuszają do ponownego czytania pytania.

Dostępność

Przycisk prfct renderuje przez Base UI natywny <button>, więc bez dodatkowej pracy przyjmuje fokus, przekazuje czytnikom ekranu swoją rolę i aktywuje się klawiszami Enter i Spacja.

KlawiszDziałanie
Tab
Przenosi fokus na przycisk. Pierścień fokusu to obrys o grubości 2 px, odsunięty od krawędzi, widoczny w każdym motywie i w trybie wymuszonych kolorów.
EnterSpacja
Aktywuje przycisk.
  • Rozmiar celu. Każdy rozmiar spełnia minimum 24 × 24 px z WCAG 2.2 (2.5.8): najmniejsze, xs i icon-xs, mają 28 px. Tych dwóch używaj tylko w gęstych interfejsach obsługiwanych głównie kursorem, bo w cel o wielkości 28 px trudno trafić palcem.
  • Kontrast. Tekst etykiety na każdym wypełnionym wariancie osiąga 4,5:1 w obu trybach; zobacz Kolor.
  • Stan wyłączony. Wyłączone przyciski wypadają z kolejności tabulacji. Jeśli użytkownik ma się dowiedzieć, dlaczego akcja jest niedostępna, zostaw przycisk włączony i wyjaśnij to po kliknięciu albo użyj focusableWhenDisabled z podpowiedzią.
  • Ładowanie. loading ustawia aria-busy="true" i zachowuje fokus; połącz go z regionem na żywo w trybie polite albo z powiadomieniem, które przekaże wynik.

Dokumentacja API

Button

Renderuje element <button>. Przyjmuje wszystkie propsy komponentu Button z Base UI. Typ propsów jest eksportowany jako ButtonProps — przyda się w komponentach, które opakowują przycisk.

PropTypDomyślnie
variant

Wizualna waga przycisku.

"default" | "brand" | "secondary" | "outline" | "ghost" | "destructive" | "link""default"
size

Wysokość i padding. Rozmiary ikonowe dają kwadratowy przycisk.

"xs" | "sm" | "default" | "lg" | "xl" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | "icon-xl""default"
loading

Pokazuje wskaźnik ładowania, ustawia aria-busy i blokuje aktywację, zachowując szerokość i fokus.

booleanfalse
disabled

Wyłącza przycisk i usuwa go z kolejności tabulacji.

booleanfalse
focusableWhenDisabled

Pozwala wyłączonemu przyciskowi przyjmować fokus, np. żeby pokazać podpowiedź z wyjaśnieniem powodu.

booleanfalse
nativeButton

Ustaw na false, gdy render tworzy element inny niż przycisk; Base UI doda wtedy role="button" i aktywację z klawiatury. Do nawigacji użyj zamiast tego linku z buttonVariants.

booleantrue
render

Podmienia renderowany element, zachowując działanie i style.

ReactElement | (props, state) => ReactElementBrak wartości domyślnej

buttonVariants

Generator klas jest eksportowany, żeby inne elementy — przede wszystkim linki — mogły wyglądać jak przyciski, nie będąc nimi. Ikony oznaczone data-icon mają rozmiar i odstępy dokładnie takie jak w Button.

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

<Link href="/pricing" className={buttonVariants({ variant: "outline", size: "sm" })}>
  Pricing
</Link>