Button Group
Łączy powiązane przyciski, pola i tekst w jedną kontrolkę, dzięki czemu odbiera się je jako jeden zestaw akcji.
import { ArchiveIcon, ClockIcon, FlagIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
export default function ButtonGroupDemo() {
return (
<ButtonGroup aria-label="Akcje wiadomości">
<Button variant="outline">
<ArchiveIcon data-icon="inline-start" />
Archiwizuj
</Button>
<Button variant="outline">
<ClockIcon data-icon="inline-start" />
Odłóż
</Button>
<Button variant="outline">
<FlagIcon data-icon="inline-start" />
Oznacz flagą
</Button>
</ButtonGroup>
)
}Anatomia
- 1GrupaŁączy swoje elementy w jedną kontrolkę: obramowania się scalają, a wewnętrzne narożniki tracą zaokrąglenie. Ma role="group" i powinna mieć aria-label.
- 2AkcjaDowolny przycisk prfct. Trzymaj się jednego wariantu w całej grupie, żeby odbierano ją jako całość.
- 3SeparatorOddziela przyciski wypełnione — nie mają one własnych obramowań, które mogłyby się zetknąć.
- 4Przycisk menuOtwiera powiązane opcje — to wzorzec przycisku dzielonego. Ma tylko ikonę, więc potrzebuje aria-label.
Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/button-group.jsonUżycie
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"<ButtonGroup aria-label="Message actions">
<Button variant="outline">Archive</Button>
<Button variant="outline">Snooze</Button>
<Button variant="outline">Flag</Button>
</ButtonGroup>Grupa usuwa zaokrąglenie wewnętrznych narożników swoich elementów i scala ich wspólne obramowania. Działa ze wszystkim, co ma atrybut data-slot — z przyciskami, polami tekstowymi, wyzwalaczami list wyboru i menu rozwijanych oraz z ButtonGroupText.
Przykłady
Orientacja
orientation="vertical" układa elementy jeden pod drugim i łączy je w pionie — to klasyczna kontrolka powiększenia.
import { MinusIcon, PlusIcon, ScanIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
export default function ButtonGroupOrientation() {
return (
<ButtonGroup orientation="vertical" aria-label="Powiększenie mapy">
<Button variant="outline" size="icon" aria-label="Powiększ">
<PlusIcon />
</Button>
<Button variant="outline" size="icon" aria-label="Pomniejsz">
<MinusIcon />
</Button>
<Button variant="outline" size="icon" aria-label="Dopasuj do ekranu">
<ScanIcon />
</Button>
</ButtonGroup>
)
}Z separatorami
Warianty wypełnione nie mają obramowań, które mogłyby się połączyć, więc dodaj między elementami ButtonGroupSeparator, żeby dało się je od siebie odróżnić.
import { ClipboardPasteIcon, CopyIcon, ScissorsIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup, ButtonGroupSeparator } from "@/components/ui/button-group"
export default function ButtonGroupWithSeparator() {
return (
<ButtonGroup aria-label="Schowek">
<Button variant="secondary" size="sm">
<ScissorsIcon data-icon="inline-start" />
Wytnij
</Button>
<ButtonGroupSeparator />
<Button variant="secondary" size="sm">
<CopyIcon data-icon="inline-start" />
Kopiuj
</Button>
<ButtonGroupSeparator />
<Button variant="secondary" size="sm">
<ClipboardPasteIcon data-icon="inline-start" />
Wklej
</Button>
</ButtonGroup>
)
}Z polami
Dołącz przycisk do pola wyszukiwania albo pola z wartością do skopiowania, a dla stałych prefiksów i sufiksów, takich jak protokoły i domeny, użyj ButtonGroupText.
import { CopyIcon, SearchIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function ButtonGroupWithInput() {
return (
<div className="flex w-full max-w-sm flex-col gap-6">
<ButtonGroup className="w-full">
<Input placeholder="Szukaj zgłoszeń" aria-label="Szukaj zgłoszeń" />
<Button variant="outline" size="icon" aria-label="Szukaj">
<SearchIcon />
</Button>
</ButtonGroup>
<div className="flex flex-col gap-2">
<Label htmlFor="subdomain">Adres URL przestrzeni roboczej</Label>
<ButtonGroup className="w-full">
<ButtonGroupText>https://</ButtonGroupText>
<Input id="subdomain" defaultValue="acme" />
<ButtonGroupText>.prfct.dev</ButtonGroupText>
<Button variant="outline" size="icon" aria-label="Kopiuj adres URL">
<CopyIcon />
</Button>
</ButtonGroup>
</div>
</div>
)
}Przycisk dzielony
Połącz akcję główną z menu alternatyw. Główny przycisk uruchamia najczęściej wybieraną opcję, a strzałka otwiera pozostałe.
import {
ChevronDownIcon,
GitMergeIcon,
GitPullRequestIcon,
LayersIcon,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup, ButtonGroupSeparator } from "@/components/ui/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export default function ButtonGroupSplitButton() {
return (
<ButtonGroup>
<Button>
<GitMergeIcon data-icon="inline-start" />
Scal pull request
</Button>
<ButtonGroupSeparator className="bg-primary-foreground/30" />
<DropdownMenu>
<DropdownMenuTrigger
render={<Button size="icon" aria-label="Więcej opcji scalania" />}
>
<ChevronDownIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-60">
<DropdownMenuGroup>
<DropdownMenuItem>
<GitMergeIcon />
Utwórz commit scalający
</DropdownMenuItem>
<DropdownMenuItem>
<LayersIcon />
Spłaszcz i scal
</DropdownMenuItem>
<DropdownMenuItem>
<GitPullRequestIcon />
Zmień bazę i scal
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
)
}<ButtonGroup>
<Button>Merge pull request</Button>
<ButtonGroupSeparator className="bg-primary-foreground/30" />
<DropdownMenu>
<DropdownMenuTrigger render={<Button size="icon" aria-label="More merge options" />}>
<ChevronDownIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">…</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>Zagnieżdżone grupy
Grupy zagnieżdżone w grupie zachowują własne połączone krawędzie i są od siebie odsunięte — tak paski narzędzi zbierają powiązane narzędzia w zestawy.
import {
AlignCenterIcon,
AlignLeftIcon,
AlignRightIcon,
BoldIcon,
ItalicIcon,
UnderlineIcon,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
export default function ButtonGroupNested() {
return (
<ButtonGroup aria-label="Formatowanie tekstu">
<ButtonGroup aria-label="Styl">
<Button variant="outline" size="icon-sm" aria-label="Pogrubienie">
<BoldIcon />
</Button>
<Button variant="outline" size="icon-sm" aria-label="Kursywa">
<ItalicIcon />
</Button>
<Button variant="outline" size="icon-sm" aria-label="Podkreślenie">
<UnderlineIcon />
</Button>
</ButtonGroup>
<ButtonGroup aria-label="Wyrównanie">
<Button variant="outline" size="icon-sm" aria-label="Wyrównaj do lewej">
<AlignLeftIcon />
</Button>
<Button variant="outline" size="icon-sm" aria-label="Wyśrodkuj">
<AlignCenterIcon />
</Button>
<Button variant="outline" size="icon-sm" aria-label="Wyrównaj do prawej">
<AlignRightIcon />
</Button>
</ButtonGroup>
</ButtonGroup>
)
}Wytyczne
Kiedy używać
- Do niewielkiego zestawu powiązanych, równorzędnych akcji na tym samym obiekcie: Archiwizuj, Odłóż, Oznacz flagą.
- Aby dołączyć akcję do pola, np. wyszukiwanie lub kopiowanie.
- Do przycisków dzielonych, w których jedna akcja jest domyślna, a pozostałe są jej odmianami.
Kiedy nie używać
- Do wyboru spośród opcji — użyj komponentu Toggle Group, który śledzi, co jest zaznaczone.
- Do akcji o różnej wadze. Grupa sugeruje równorzędność; akcja główna i przycisk anulowania powinny być osobnymi przyciskami z odstępem między nimi.
- Do długich list akcji. Powyżej czterech–pięciu elementów przenieś resztę do menu Dropdown Menu.
Zachowaj spójne warianty
Wszystkie elementy grupy powinny mieć ten sam wariant i ten sam rozmiar. Różne warianty rozbijają wspólną sylwetkę, dzięki której grupę odbiera się jako jedną kontrolkę.
Dostępność
- Grupa renderuje
role="group". Nadaj jejaria-label(lubaria-labelledby), który nazywa zestaw — Akcje wiadomości, Powiększenie mapy — żeby czytnik ekranu ogłosił kontekst, gdy fokus wejdzie do grupy. - Każdy element zachowuje własną semantykę i własne miejsce w kolejności tabulacji. W odróżnieniu od Toggle Group nie ma tu wędrującego fokusu (roving focus): przyciski w grupie to niezależne akcje.
- Przyciski z samą ikoną wewnątrz grupy nadal potrzebują
aria-label. - Element z fokusem trafia na wierzch, więc sąsiednie obramowanie nigdy nie przycina pierścienia fokusu.
| Klawisz | Działanie |
|---|---|
TabShiftTab | Przenosi fokus między przyciskami, polami i wyzwalaczami w grupie. |
EnterSpacja | Aktywuje przycisk, na którym jest fokus. |
Dokumentacja API
ButtonGroup
Renderuje <div> z role="group" i data-slot="button-group".
ButtonGroupText
Nieinteraktywny segment na prefiksy, sufiksy i etykiety. Renderuje <div>; użyj render, aby podmienić element.
ButtonGroupSeparator
Cienka linia między elementami dla wariantów bez obramowań. Zbudowany na komponencie Separator.
buttonGroupVariants
Generator klas, eksportowany dla własnych elementów, które mają łączyć się tak jak grupa.