קבוצת כפתורים
Fuses מאחד כפתורים מפוצלים, סרגלי כלים ותוספים להזנת נתונים ליחידה אחת ממוסגרת — מסגרת עבה אחת, צל קשיח אחד, לחיצה משותפת אחת.
"use client"
import * as React from "react"ה-button group מאחד כפתורים, שדות ולייבלים סמוכים לבקרה מקטעת אחת. אין פרימיטיב מתחת — מיכל role="group" פשוט מחזיק את המסגרת הנאו-ברוטליסטית (מסגרת עבה אחת, צל חד אחד, אנימציית לחיצה משותפת); רק ButtonGroupSeparator מגיע לפרימיטיב, ועוטף Separator.
השתמשו בו כש:
- כפתורים מפוצלים — פעולה ראשית ועוד
DropdownMenuשל חלופות: שמירה / שמירה בשם…, Merge / Squash. - דפדוף וסרגלי כלים — הקודם/הבא, זום פנימה/החוצה, מחליפי תצוגה באריזה אחת.
- תוספות לשדות — שדה חיפוש עם כפתור שליחה, תווית קידומת URL, כפתור העתקה בסיומת.
התקנה#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/button-group.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/button-group.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/button-group.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/button-group.json
התקינו את התלויות הבאות:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
העתיקו והדביקו את הקוד הבא לתוך הפרויקט שלכם.
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
const buttonGroupVariants = cva(
cn(
// The GROUP itself is the cohesive NeoBrutalist unit: a single bold border +
// one hard offset shadow wrapping every segment, so it reads as ONE unit.
// The frame animates so the whole group can press like a single Button.
"relative inline-flex w-fit items-stretch border-2 border-foreground bg-background shadow-md transition-all duration-200",
// Segments become flush fills — the frame owns the border, shadow and corners.
"[&>*]:rounded-none",
"[&>:is(button,a)]:border-0 [&>:is(button,a)]:shadow-none!",
// Segments never translate on their own — only the whole frame moves.
"[&>:is(button,a)]:hover:translate-y-0! [&>:is(button,a)]:active:translate-x-0! [&>:is(button,a)]:active:translate-y-0!",
// The WHOLE group presses into its shadow like a single Button: hover sinks it
// toward the shadow, press sits it flush (shadow gone).
"has-[:is(button,a):hover]:translate-y-1 has-[:is(button,a):hover]:shadow",
"has-[:is(button,a):active]:translate-y-2 has-[:is(button,a):active]:translate-x-1 has-[:is(button,a):active]:shadow-none",
// When a group CONTAINS nested groups it becomes a plain gapped container
// (no frame / shadow / press) so each nested sub-group is its own pressable
// unit instead of pressing the whole outer frame.
"has-[>[data-slot=button-group]]:gap-2 has-[>[data-slot=button-group]]:border-0 has-[>[data-slot=button-group]]:bg-transparent has-[>[data-slot=button-group]]:shadow-none! has-[>[data-slot=button-group]]:translate-x-0! has-[>[data-slot=button-group]]:translate-y-0!",
// The hovered/pressed segment darkens in place via a full-bleed inset tint, so
// you can still tell which segment you're acting on while the frame presses.
"[&>:is(button,a):hover]:shadow-[inset_0_0_0_999px_#00000012]! [&>:is(button,a):active]:shadow-[inset_0_0_0_999px_#0000001f]!",
"dark:[&>:is(button,a):hover]:shadow-[inset_0_0_0_999px_#ffffff1f]! dark:[&>:is(button,a):active]:shadow-[inset_0_0_0_999px_#ffffff33]!",
// Solid dark segments (secondary) always lighten on hover — a dark tint would
// be invisible on a black button in light mode.
"[&>:is(button,a)[data-variant=secondary]:hover]:shadow-[inset_0_0_0_999px_#ffffff2b]! [&>:is(button,a)[data-variant=secondary]:active]:shadow-[inset_0_0_0_999px_#ffffff45]!",
"[&>:is(button,a):focus-visible]:relative [&>:is(button,a):focus-visible]:z-10"
),
{
variants: {
orientation: {
// A single crisp seam between segments (logical side, so RTL-correct).
horizontal:
"flex-row [&>*:not(:first-child)]:border-s-2! [&>*:not(:first-child)]:border-foreground",
vertical:
"flex-col [&>*:not(:first-child)]:border-t-2! [&>*:not(:first-child)]:border-foreground",
},
},
defaultVariants: {
orientation: "horizontal",
},
}
)
function ButtonGroup({
className,
orientation,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
return (
<div
role="group"
data-slot="button-group"
data-orientation={orientation ?? "horizontal"}
className={cn(buttonGroupVariants({ orientation }), className)}
{...props}
/>
)
}
function ButtonGroupText({
className,
asChild = false,
...props
}: React.ComponentProps<"div"> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "div"
return (
<Comp
data-slot="button-group-text"
className={cn(
"inline-flex items-center gap-2 bg-muted px-3 font-head text-sm font-medium text-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function ButtonGroupSeparator({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="button-group-separator"
orientation={orientation}
className={cn(
"relative z-10 self-stretch bg-border data-horizontal:h-0.5 data-horizontal:w-auto data-vertical:h-auto data-vertical:w-0.5",
className
)}
{...props}
/>
)
}
export {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
buttonGroupVariants,
}
עדכנו את נתיבי הייבוא כך שיתאימו למבנה הפרויקט שלכם.
שימוש#
import {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
} from "@/components/ui/button-group"<ButtonGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>קומפוזיציה#
השתמשו בקומפוזיציה הבאה כדי לבנות ButtonGroup:
ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupTextנגישות#
- לרכיב
ButtonGroupיש את התכונהroleהמוגדרת ל-group. - השתמשו ב-Tab כדי לנווט בין הכפתורים בקבוצה.
- השתמשו ב-
aria-labelאוaria-labelledbyכדי לתייג את קבוצת הכפתורים.
<ButtonGroup aria-label="Button group">
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>ButtonGroup vs ToggleGroup#
- השתמשו ברכיב
ButtonGroupכשאתם רוצים לקבץ כפתורים שמבצעים פעולה. - השתמשו ברכיב
ToggleGroupכשאתם רוצים לקבץ כפתורים שמחליפים מצב.
דוגמאות#
Orientation#
הגדירו את המאפיין orientation כדי לשנות את פריסת קבוצת הכפתורים.
import { MinusIcon, PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"גודל#
שלטו בגודל הכפתורים באמצעות המאפיין size על כפתורים בודדים.
import { PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"מקונן#
קננו רכיבי <ButtonGroup> כדי ליצור קבוצות כפתורים עם ריווח.
import { AudioLinesIcon, PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"מפריד#
רכיב ButtonGroupSeparator מחלק חזותית את הכפתורים בתוך קבוצה.
כפתורים בווריאנט outline אינם זקוקים למפריד מכיוון שיש להם מסגרת. עבור וריאנטים אחרים, מומלץ להוסיף מפריד כדי לשפר את ההיררכיה החזותית.
import { Button } from "@/components/ui/button"
import {
ButtonGroup,מפוצל#
צרו קבוצת כפתורים מפוצלת על ידי הוספת שני כפתורים המופרדים באמצעות ButtonGroupSeparator.
import { IconPlus } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"Input#
עטפו רכיב Input בכפתורים.
import { SearchIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Input Group#
עטפו רכיב InputGroup כדי ליצור פריסות קלט מורכבות.
"use client"
import * as React from "react"Dropdown Menu#
צרו קבוצת כפתורים מפוצלת עם רכיב DropdownMenu.
"use client"
import {Select#
שלבו עם רכיב Select.
"use client"
import * as React from "react"Popover#
השתמשו עם רכיב Popover.
import { BotIcon, ChevronDownIcon } from "lucide-react"
import { Button } from "@/components/ui/button"RTL#
כדי להפעיל RTL ב-Neobrutalism, עיינו במדריך ההגדרה של RTL.
"use client"
import * as React from "react"נגישות#
ButtonGroup מרנדר role="group", שאומר לטכנולוגיה מסייעת שהכפתורים שייכים יחד בלי לשנות התנהגות מקלדת — כל כפתור שומר על tab stop משלו והפעלה מקורית של Enter / Space. תנו שם לקבוצה עם aria-label או aria-labelledby כדי שקוראי מסך יכריזו על ההקשר:
<ButtonGroup aria-label="Pagination">
<Button>Previous</Button>
<Button>Next</Button>
</ButtonGroup>ButtonGroupSeparator עוטף את Separator של Base UI, שמרנדר role="separator" עם aria-orientation מתאים — מוכרז כמפריד, אף פעם לא ניתן לפוקוס. אם אתם צריכים tab stop יחיד עם ניווט במקשי חצים בין כפתורים, זה דפוס Toolbar של WAI-ARIA — הרכיב הזה משאיר במכוון סדר tab רגיל.
תיעוד ה-API#
ButtonGroup#
רכיב ButtonGroup הוא מכל שמקבץ יחד כפתורים קשורים בעיצוב אחיד.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
<ButtonGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>קננו כמה קבוצות כפתורים כדי ליצור פריסות מורכבות עם ריווח. עיינו בדוגמה מקונן לפרטים נוספים.
<ButtonGroup>
<ButtonGroup />
<ButtonGroup />
</ButtonGroup>ButtonGroupSeparator#
רכיב ButtonGroupSeparator מחלק חזותית את הכפתורים בתוך קבוצה.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "vertical" |
<ButtonGroup>
<Button>Button 1</Button>
<ButtonGroupSeparator />
<Button>Button 2</Button>
</ButtonGroup>ButtonGroupText#
השתמשו ברכיב זה כדי להציג טקסט בתוך קבוצת כפתורים.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
<ButtonGroup>
<ButtonGroupText>Text</ButtonGroupText>
<Button>Button</Button>
</ButtonGroup>השתמשו במאפיין asChild כדי להציג רכיב מותאם אישית בתור הטקסט, לדוגמה תווית.
import { ButtonGroupText } from "@/components/ui/button-group"
import { Label } from "@/components/ui/label"
export function ButtonGroupTextDemo() {
return (
<ButtonGroup>
<ButtonGroupText asChild>
<Label htmlFor="name">Text</Label>
</ButtonGroupText>
<Input placeholder="Type something here..." id="name" />
</ButtonGroup>
)
}