Düğme grubu
Düğmeleri, araç çubuklarını ve giriş eklentilerini tek bir çerçeveli birim halinde birleştirir — tek bir kalın kenarlık, tek bir keskin gölge, tek bir ortak tıklama.
"use client"
import * as React from "react"Buton grubu, bitişik butonları, girişleri ve etiketleri tek bir bölmeli denetimde birleştirir. Altında primitif yoktur — neobrütalist çerçevenin sahibi düz bir role="group" kapsayıcısıdır (tek kalın kenarlık, tek sert gölge, tek ortak basılma animasyonu); yalnızca ButtonGroupSeparator bir primitife uzanır ve Separator'ı sarar.
Şu durumlarda tercih edin:
- Bölünmüş butonlar — birincil eylem artı alternatifleri sunan bir
DropdownMenu: Kaydet / Farklı kaydet…, Merge / Squash. - Sayfalayıcılar ve araç çubukları — Önceki/Sonraki, yakınlaştırma/uzaklaştırma, tek çerçevede toplanmış görünüm değiştiriciler.
- Giriş eklentileri — gönder butonuyla birlikte bir arama alanı, bir URL öneki etiketi, sona eklenmiş bir kopyalama butonu.
Kurulum#
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
Şu bağımlılıkları kurun:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
Aşağıdaki kodu kopyalayıp projenize yapıştırın.
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 yollarını projenizin yapısına uyacak şekilde güncelleyin.
Kullanım#
import {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
} from "@/components/ui/button-group"<ButtonGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>Kompozisyon#
Bir ButtonGroup oluşturmak için şu kompozisyonu kullanın:
ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupTextButtonGroup vs ToggleGroup#
- Bir eylem gerçekleştiren butonları gruplamak istediğinizde
ButtonGroupbileşenini kullanın. - Bir durumu değiştiren butonları gruplamak istediğinizde
ToggleGroupbileşenini kullanın.
Örnekler#
Yönelim#
Buton grubunun düzenini değiştirmek için orientation prop'unu ayarlayın.
import { MinusIcon, PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Boyut#
Butonların boyutunu, tek tek butonlardaki size prop'unu kullanarak kontrol edin.
import { PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Yuvalanmış#
Aralıklı buton grupları oluşturmak için <ButtonGroup> bileşenlerini iç içe yerleştirin.
import { AudioLinesIcon, PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Ayırıcı#
ButtonGroupSeparator bileşeni, bir grup içindeki butonları görsel olarak böler.
outline varyantlı butonların ayırıcıya ihtiyacı yoktur, çünkü zaten bir kenarlıkları vardır. Diğer varyantlar için görsel hiyerarşiyi iyileştirmek amacıyla bir ayırıcı önerilir.
import { Button } from "@/components/ui/button"
import {
ButtonGroup,Bölünmüş#
Bir ButtonGroupSeparator ile ayrılmış iki buton ekleyerek bölünmüş bir buton grubu oluşturun.
import { IconPlus } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"Input#
Bir Input bileşenini butonlarla sarın.
import { SearchIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Input Group#
Karmaşık girdi düzenleri oluşturmak için bir InputGroup bileşenini sarın.
"use client"
import * as React from "react"Dropdown Menu#
Bir DropdownMenu bileşeniyle bölünmüş bir buton grubu oluşturun.
"use client"
import {Select#
Bir Select bileşeniyle eşleştirin.
"use client"
import * as React from "react"Popover#
Bir Popover bileşeniyle kullanın.
import { BotIcon, ChevronDownIcon } from "lucide-react"
import { Button } from "@/components/ui/button"RTL#
Neobrutalism'de RTL desteğini etkinleştirmek için RTL yapılandırma kılavuzuna bakın.
"use client"
import * as React from "react"Erişilebilirlik#
ButtonGroup, role="group" işler; bu, klavye davranışını değiştirmeden yardımcı teknolojilere butonların bir bütün olduğunu söyler — her buton kendi sekme durağını ve doğal Enter / Space etkinleştirmesini korur. Ekran okuyucuların bağlamı duyurabilmesi için gruba aria-label veya aria-labelledby ile bir ad verin:
<ButtonGroup aria-label="Pagination">
<Button>Previous</Button>
<Button>Next</Button>
</ButtonGroup>ButtonGroupSeparator, Base UI Separator'ı sarar; o da uygun aria-orientation ile role="separator" işler — bir ayraç olarak duyurulur ve asla odak almaz. Butonlar arasında ok tuşlarıyla gezinilen tek bir sekme durağına ihtiyacınız varsa aradığınız şey WAI-ARIA Toolbar desenidir — bu bileşen bilinçli olarak düz sekme sırasını korur.
API Referansı#
ButtonGroup#
ButtonGroup bileşeni, ilişkili butonları tutarlı bir stille bir araya getiren bir kapsayıcıdır.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
<ButtonGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>Aralıklı karmaşık düzenler oluşturmak için birden fazla buton grubunu iç içe yerleştirin. Daha fazla ayrıntı için yuvalanmış örneğine bakın.
<ButtonGroup>
<ButtonGroup />
<ButtonGroup />
</ButtonGroup>ButtonGroupSeparator#
ButtonGroupSeparator bileşeni, bir grup içindeki butonları görsel olarak böler.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "vertical" |
<ButtonGroup>
<Button>Button 1</Button>
<ButtonGroupSeparator />
<Button>Button 2</Button>
</ButtonGroup>ButtonGroupText#
Bir buton grubu içinde metin göstermek için bu bileşeni kullanın.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
<ButtonGroup>
<ButtonGroupText>Text</ButtonGroupText>
<Button>Button</Button>
</ButtonGroup>Metin olarak özel bir bileşen, örneğin bir label render etmek için asChild prop'unu kullanın.
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>
)
}