گروه دکمه
فوزها دکمههای شکافته، نوار ابزارها و افزونههای ورودی را در یک واحد قابدار ادغام میکنند — یک حاشیهٔ ضخیم، یک سایهٔ سخت، یک فشار مشترک.
"use client"
import * as React from "react"گروه دکمه، دکمهها، ورودیها و برچسبهای مجاور را در یک کنترل segmented ادغام میکند. primitive زیرین ندارد — یک کانتینر ساده با role="group" قاب نئوبروتالیستی را نگه میدارد (یک حاشیهٔ ضخیم، یک سایهٔ سخت، یک انیمیشن فشردن مشترک)؛ فقط ButtonGroupSeparator به primitive میرسد و Separator را میپیچد.
برای این موارد از آن استفاده کنید:
- دکمههای split — اقدام اصلی بههمراه
DropdownMenuاز جایگزینها: Save / Save as…، Merge / Squash. - Pager و نوار ابزار — Previous/Next، زوم داخل/خارج، سوئیچر نما در یک قاب.
- افزونهٔ ورودی — فیلد جستجو با دکمهٔ submit، برچسب پیشوند 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 را متناسب با ساختار پروژهٔ خود بهروزرسانی کنید.
استفاده#
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 را تنظیم کنید تا چیدمان گروه دکمهها تغییر کند.
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 جداکنندهٔ Base UI را میپیچد که role="separator" با aria-orientation متناظر رندر میکند — بهعنوان divider اعلام میشود و هرگز focusable نیست. اگر یک tab stop واحد با ناوبری کلید جهت بین دکمهها میخواهید، آن الگوی WAI-ARIA Toolbar است — این کامپوننت عمداً ترتیب 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 برای رندر یک کامپوننت سفارشی بهعنوان متن استفاده کنید، برای مثال یک label.
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>
)
}