İçeriğe atla

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.json
npx shadcn@latest add https://neobrutalism.com/r/base/button-group.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/button-group.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/button-group.json

Şu bağımlılıkları kurun:

pnpm add @base-ui/react
npm install @base-ui/react
yarn add @base-ui/react
bun add @base-ui/react

Aşağıdaki kodu kopyalayıp projenize yapıştırın.

components/ui/button-group.tsx
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
└── ButtonGroupText

ButtonGroup vs ToggleGroup

  • Bir eylem gerçekleştiren butonları gruplamak istediğinizde ButtonGroup bileşenini kullanın.
  • Bir durumu değiştiren butonları gruplamak istediğinizde ToggleGroup bileş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"

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.

PropTypeDefault
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.

PropTypeDefault
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.

PropTypeDefault
asChildbooleanfalse
<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>
  )
}