shadcn/ui'da birbiriyle ilişkili butonları tek bir kutuda birleştirmek için ButtonGroup, açık ya da kapalı olabilen tek bir düğme için Toggle, bu düğmelerden bir set oluşturmak için de ToggleGroup kullanılır. Belgelerin ayrımı açıktır: bir işlem yapan butonları gruplarken ButtonGroup, bir durumu açıp kapatan butonları gruplarken ToggleGroup seçilir. Üçü de npx shadcn@latest add komutuyla projenize ayrı dosyalar olarak eklenir.

Bu yazıda önce ButtonGroup ve yardımcıları ButtonGroupSeparator ile ButtonGroupText bileşenlerini, ardından Toggle ve ToggleGroup bileşenlerini belge sayfaları ve depodaki kaynak kodla anlatıyoruz: yön (orientation), iç içe gruplar, ayırıcı, bölünmüş buton, input ile birlikte kullanım, type="single" ile type="multiple" farkı ve kontrollü kullanım. Tek bir butonun varyantları için Button yazısına, serinin geri kalanı için shadcn/ui öğrenme rehberi sayfasına bakabilirsiniz.

ButtonGroup: kurulum ve temel kullanım

ButtonGroup: kurulum ve temel kullanım

ButtonGroup, Ekim 2025'te yayımlanan yeni bileşenler arasında yer alır; değişiklik günlüğü onu eylem grupları ve bölünmüş butonlar için bir kapsayıcı olarak tanıtır. CLI ile eklemek için:

bash
npx shadcn@latest add button-group

Elle kurulumda belgeler yalnızca @radix-ui/react-slot paketini kurmanızı ister; çünkü ButtonGroupText bileşeni asChild desteği için Slot kullanır. Kaynak ayrıca separator bileşenini içe aktardığı için o dosyanın da projede olması gerekir. Kullanım şöyledir:

typescript
import { Button } from "@/components/ui/button"
import {
  ButtonGroup,
  ButtonGroupSeparator,
  ButtonGroupText,
} from "@/components/ui/button-group"

export function DosyaIslemleri() {
  return (
    <ButtonGroup aria-label="Dosya işlemleri">
      <Button variant="outline">Arşivle</Button>
      <Button variant="outline">Bildir</Button>
    </ButtonGroup>
  )
}

Grup, kaynakta role="group" taşıyan bir div öğesidir. Görsel birleştirmeyi sağlayan şey buttonGroupVariants içindeki seçicilerdir: yatay yönde ilk öğe dışındaki tüm çocukların sol köşe yuvarlaklığı ve sol çerçevesi kaldırılır, son öğe dışındakilerin de sağ köşesi düzleştirilir. Böylece ayrı butonlar tek parça bir şerit gibi görünür. Odaklanan öğe focus-visible:z-10 ile öne alınır, bu sayede odak halkası komşu butonun altında kalmaz.

Erişilebilirlik

Belge sayfası üç noktayı vurgular: bileşen role="group" özniteliğine sahiptir, gruptaki butonlar arasında Tab tuşuyla gezinilir ve grubu tanımlamak için aria-label ya da aria-labelledby kullanılmalıdır. Grup rolünün ve etiketlemenin genel anlamı için ARIA yazısına, klavyeyle erişimin önemi için WCAG temelleri yazısına göz atabilirsiniz. Gruptaki ikon butonlarına da tek tek aria-label vermeyi unutmayın; belgedeki örneklerde "Go Back", "More Options" gibi etiketler kullanılır.

Yön ve boyut

orientation özelliği iki değer alır: "horizontal" (varsayılan) ve "vertical". Dikey yönde grup flex-col olur ve köşe düzleştirme kuralları üst ve alt kenarlara uygulanır. Belgedeki örnek, artı ve eksi ikonlu iki butonla dikey bir medya kontrolü kurar:

typescript
import { MinusIcon, PlusIcon } from "lucide-react"

<ButtonGroup orientation="vertical" aria-label="Ses kontrolleri" className="h-fit">
  <Button variant="outline" size="icon" aria-label="Sesi aç">
    <PlusIcon />
  </Button>
  <Button variant="outline" size="icon" aria-label="Sesi kıs">
    <MinusIcon />
  </Button>
</ButtonGroup>

Grubun kendisinde bir size özelliği yoktur. Belgeler boyutu gruptaki her butona ayrı ayrı size vererek ayarlamanızı söyler. Bu yüzden küçük bir grup kurarken metinli butonlara size="sm", ikonlu butona size="icon-sm" verirsiniz; bu iki boyut aynı yükseklikte olduğu için şerit düzgün görünür.

İç içe gruplar ve ayırıcı

Bir ButtonGroup içine başka ButtonGroup öğeleri koyduğunuzda iç gruplar arasında boşluk oluşur. Bunu sağlayan kaynak kuralı has-[>[data-slot=button-group]]:gap-2 ifadesidir: doğrudan çocukları arasında grup bulunan bir grup, çocuklarını 2 birim aralıkla dizer. Belgedeki örnek, sayfa numaralarını bir grupta, önceki ve sonraki oklarını başka bir grupta tutar.

typescript
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"

<ButtonGroup>
  <ButtonGroup>
    <Button variant="outline" size="sm">1</Button>
    <Button variant="outline" size="sm">2</Button>
    <Button variant="outline" size="sm">3</Button>
  </ButtonGroup>
  <ButtonGroup>
    <Button variant="outline" size="icon-sm" aria-label="Önceki">
      <ArrowLeftIcon />
    </Button>
    <Button variant="outline" size="icon-sm" aria-label="Sonraki">
      <ArrowRightIcon />
    </Button>
  </ButtonGroup>
</ButtonGroup>

ButtonGroupSeparator, gruptaki butonların arasına görsel bir çizgi koyar. Belgeler burada önemli bir ayrıntı verir: outline varyantındaki butonların zaten çerçevesi olduğu için ayırıcıya ihtiyacı yoktur; diğer varyantlarda ise görsel hiyerarşiyi güçlendirmek için ayırıcı önerilir. Ayırıcının varsayılan yönü "vertical"'dır, çünkü yatay bir grupta dikey bir çizgi gerekir.

typescript
<ButtonGroup>
  <Button variant="secondary" size="sm">Kopyala</Button>
  <ButtonGroupSeparator />
  <Button variant="secondary" size="sm">Yapıştır</Button>
</ButtonGroup>

Bölünmüş buton (split button)

Bölünmüş buton, ana işlemi yapan bir buton ile yanında ek seçenekler sunan küçük bir butondan oluşur. Belgedeki "Split" örneği bunu iki secondary buton ve aralarına konan bir ayırıcıyla kurar. Ek seçenekleri bir menüde göstermek isterseniz "Dropdown Menu" örneğindeki gibi ikinci butonu DropdownMenuTrigger asChild içine yerleştirirsiniz; menü bileşeninin kendisi Dropdown Menu yazısında.

typescript
import { IconPlus } from "@tabler/icons-react"

<ButtonGroup>
  <Button variant="secondary">Yeni kayıt</Button>
  <ButtonGroupSeparator />
  <Button size="icon" variant="secondary" aria-label="Daha fazla">
    <IconPlus />
  </Button>
</ButtonGroup>

ButtonGroupText ve input ile kullanım

ButtonGroupText, grubun içinde metin göstermek için kullanılan, soluk arka planlı ve çerçeveli bir kutudur. asChild özelliği sayesinde bu kutuyu bir Label öğesine dönüştürebilirsiniz; belgedeki örnekte metin, yanındaki input'un etiketi olarak çalışır. Grup ayrıca doğrudan çocuk olan bir input öğesine flex-1 verir, yani input kalan genişliği doldurur.

typescript
import { 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 function AramaSatiri() {
  return (
    <ButtonGroup>
      <ButtonGroupText asChild>
        <Label htmlFor="ara">Ara</Label>
      </ButtonGroupText>
      <Input id="ara" placeholder="Ürün adı..." />
      <Button variant="outline" aria-label="Ara">
        <SearchIcon />
      </Button>
    </ButtonGroup>
  )
}

Belgelerde ayrıca grubun InputGroup, Select ve Popover ile birlikte kullanıldığı örnekler var. Select örneğinde para birimi seçicisi ile tutar alanı bir iç grupta, gönder butonu başka bir iç grupta durur. Kaynak, grubun içindeki select tetikleyicisine genişlik verilmemişse w-fit uygular. Input grubunun kendisi Input ve Input Group yazısında, select ise Select yazısında anlatılıyor.

Toggle: iki durumlu buton

Toggle: iki durumlu buton: variant default, variant outline, size sm, size default

Toggle, açık ya da kapalı olabilen bir düğmedir ve Radix'in Toggle ilkeli üzerine kuruludur. Kurulum komutu npx shadcn@latest add toggle, elle kurulumda gereken paket @radix-ui/react-toggle'dır. Kaynaktaki toggleVariants iki varyant (default, outline) ve üç boyut (default, sm, lg) tanımlar. Açık durumda Radix öğeye data-state="on" yazar; temel sınıflardaki data-[state=on]:bg-accent kuralı da düğmeyi vurgular.

typescript
import { Italic } from "lucide-react"

import { Toggle } from "@/components/ui/toggle"

export function ItalikDugmesi() {
  return (
    <Toggle aria-label="İtalik">
      <Italic />
      İtalik
    </Toggle>
  )
}

Belgedeki ana örnek, açık durumdaki görünümü className ile değiştirir: arka plan rengini kaldırır ve ikonu maviyle doldurur. Bunun için data-[state=on]: önekli sınıflar kullanılır. Bu yöntem, durum bilgisinin bir data-* özniteliğinde tutulmasının güzel bir sonucudur; ek JavaScript yazmadan açık ve kapalı durumlar için farklı stil verebilirsiniz.

typescript
import { BookmarkIcon } from "lucide-react"

<Toggle
  aria-label="Yer imi"
  size="sm"
  variant="outline"
  className="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-blue-500 data-[state=on]:*:[svg]:stroke-blue-500"
>
  <BookmarkIcon />
  Yer imi
</Toggle>
Toggle değeriKaynaktaki sınıflar
variant defaultbg-transparent
variant outlineborder border-input bg-transparent shadow-xs, üzerine gelince bg-accent
size smh-8 px-1.5 min-w-8
size defaulth-9 px-2 min-w-9
size lgh-10 px-2.5 min-w-10

Tablodaki yükseklikler bilinçli olarak butonla aynıdır: sm toggle ile sm buton 8, varsayılanlar 9, lg boyutlar 10 birim yüksekliktedir. Bu sayede bir araç çubuğunda toggle'ları ve butonları yan yana koyduğunuzda satır hizası bozulmaz. min-w-* sınıfları ise yalnızca ikon içeren bir toggle'ın kare görünmesini sağlar.

Toggle'ın açık olup olmadığını kodda izlemek için kullanılacak özellikler Radix Toggle API'sinde tanımlıdır; shadcn/ui belge sayfası bunları ayrıca listelemez ve sayfanın üst kısmındaki bağlantıyla Radix belgesine yönlendirir. disabled özelliği ise belgedeki "Disabled" örneğinde gösterilir ve temel sınıflardaki disabled:opacity-50 ile soluklaştırılır.

ToggleGroup: tekli ve çoklu seçim

ToggleGroup, bir dizi toggle düğmesini tek bir grupta toplar. Kurulum npx shadcn@latest add toggle-group, elle kurulumda gereken paket @radix-ui/react-toggle-group'dır. En önemli özellik type'tır: "single" değerinde aynı anda yalnızca bir öğe açık olabilir, "multiple" değerinde birden çok öğe açılabilir. Belgedeki örnek kalın, italik ve altı çizili biçimlendirme düğmeleriyle çoklu seçimi gösterir:

typescript
import { Bold, Italic, Underline } from "lucide-react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function BicimDugmeleri() {
  return (
    <ToggleGroup variant="outline" type="multiple">
      <ToggleGroupItem value="bold" aria-label="Kalın">
        <Bold className="h-4 w-4" />
      </ToggleGroupItem>
      <ToggleGroupItem value="italic" aria-label="İtalik">
        <Italic className="h-4 w-4" />
      </ToggleGroupItem>
      <ToggleGroupItem value="underline" aria-label="Altı çizili">
        <Underline className="h-4 w-4" />
      </ToggleGroupItem>
    </ToggleGroup>
  )
}

Her ToggleGroupItem için value zorunludur ve belge, erişilebilirlik için her öğeye aria-label eklemenizi hatırlatır. Grubun variant ve size değerleri, kaynakta bir React context ile öğelere aktarılır; bu yüzden değerleri bir kez grupta yazmanız yeterlidir. Öğelerin görünümü toggle.tsx içindeki toggleVariants fonksiyonundan gelir, yani toggle-group dosyası toggle bileşenine bağımlıdır.

Prop (ToggleGroup)TürVarsayılan
typesingle veya multiplesingle
variantdefault veya outlinedefault
sizedefault, sm veya lgdefault
spacingnumber0
classNamestringyok

spacing ve kontrollü kullanım

spacing değeri 0 iken öğeler birleşik bir şerit oluşturur: köşeler yalnızca ilk ve son öğede yuvarlaktır, outline varyantında aradaki çift çerçeveler kaldırılır. Belgedeki gibi spacing={2} verdiğinizde öğeler ayrı düğmeler gibi aralıklı dizilir. Kaynak bu değeri --gap CSS değişkenine yazar ve aralığı onunla hesaplar.

Seçili değeri React durumunda tutmak için depodaki dashboard bloğu iyi bir örnektir. Orada type="single" bir grup, value ve onValueChange ile bir useState değerine bağlanır; aynı durum, dar ekranlarda gösterilen bir Select bileşenine de bağlanır:

typescript
"use client"

import * as React from "react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ZamanAraligi() {
  const [timeRange, setTimeRange] = React.useState("90d")

  return (
    <ToggleGroup
      type="single"
      value={timeRange}
      onValueChange={setTimeRange}
      variant="outline"
    >
      <ToggleGroupItem value="90d">Son 3 ay</ToggleGroupItem>
      <ToggleGroupItem value="30d">Son 30 gün</ToggleGroupItem>
      <ToggleGroupItem value="7d">Son 7 gün</ToggleGroupItem>
    </ToggleGroup>
  )
}

Mini örnek: metin editörü araç çubuğu

İki bileşen ailesinin farkını en iyi gösteren yer bir metin editörünün araç çubuğudur. Geri al ve yinele birer işlemdir, bir durum tutmazlar; bu yüzden bir ButtonGroup içinde durur. Kalın, italik ve altı çizili ise açılıp kapanan biçimlerdir ve aynı anda birkaçı açık olabilir; bu yüzden type="multiple" bir ToggleGroup kullanılır. Hizalama (sola, ortaya, sağa) da bir durumdur ama aynı anda yalnızca biri seçilebilir; ona type="single" uygundur. Aşağıdaki örnek bu üç parçayı belgedeki bileşenlerle birleştirir:

typescript
import {
  AlignCenter,
  AlignLeft,
  AlignRight,
  Bold,
  Italic,
  Redo2,
  Underline,
  Undo2,
} from "lucide-react"

import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function EditorAracCubugu() {
  return (
    <div className="flex items-center gap-2">
      <ButtonGroup aria-label="Geçmiş">
        <Button variant="outline" size="icon" aria-label="Geri al">
          <Undo2 />
        </Button>
        <Button variant="outline" size="icon" aria-label="Yinele">
          <Redo2 />
        </Button>
      </ButtonGroup>
      <ToggleGroup type="multiple" variant="outline" aria-label="Biçim">
        <ToggleGroupItem value="bold" aria-label="Kalın">
          <Bold />
        </ToggleGroupItem>
        <ToggleGroupItem value="italic" aria-label="İtalik">
          <Italic />
        </ToggleGroupItem>
        <ToggleGroupItem value="underline" aria-label="Altı çizili">
          <Underline />
        </ToggleGroupItem>
      </ToggleGroup>
      <ToggleGroup type="single" variant="outline" defaultValue="left" aria-label="Hizalama">
        <ToggleGroupItem value="left" aria-label="Sola hizala">
          <AlignLeft />
        </ToggleGroupItem>
        <ToggleGroupItem value="center" aria-label="Ortala">
          <AlignCenter />
        </ToggleGroupItem>
        <ToggleGroupItem value="right" aria-label="Sağa hizala">
          <AlignRight />
        </ToggleGroupItem>
      </ToggleGroup>
    </div>
  )
}

Butonlar ve toggle öğeleri aynı yükseklikte olduğu için (ikisi de varsayılan boyutta 9 birim) satır düzgün hizalanır. İkonlara ayrıca boyut sınıfı vermeye gerek yoktur; hem buttonVariants hem toggleVariants, size-* sınıfı olmayan SVG'leri 4 birime ayarlar. defaultValue, kontrolsüz kullanımda başlangıçta seçili öğeyi belirler; Radix ToggleGroup'un bu ve benzeri özellikleri için sayfadaki Radix bağlantısına bakabilirsiniz.

ButtonGroup API özeti

BileşenPropTürVarsayılan
ButtonGrouporientationhorizontal veya verticalhorizontal
ButtonGroupSeparatororientationhorizontal veya verticalvertical
ButtonGroupTextasChildbooleanfalse

ButtonGroup mı, ToggleGroup mı?

ButtonGroup mı, ToggleGroup mı? Amaç, Durum tutar mı?, Radix ilkeli, Boyut nereden?
SoruButtonGroupToggleGroup
Amaçİşlem yapan butonları gruplamakDurum açıp kapatan düğmeleri gruplamak
Durum tutar mı?Hayır, butonlar yalnızca tıklama olayı üretirEvet, seçili değer veya değerler
Radix ilkeliYok (div, role group)@radix-ui/react-toggle-group
Boyut nereden?Her butonun kendi size değerindenGrubun size değerinden
Yönorientation horizontal veya verticalBelgede ayrıca anlatılmaz

Sık yapılan hatalar

  • Grubun etiketini atlamak. Belge, ButtonGroup için aria-label ya da aria-labelledby önerir; ikon düğmelerine de tek tek etiket verin.
  • Grupta size aramak. ButtonGroup'ta boyut özelliği yoktur; boyutu butonlara verin. ToggleGroup'ta ise boyut gruba verilir.
  • Outline butonlar arasına ayırıcı koymak. Çerçeveler zaten ayrım sağlar; belge ayırıcıyı diğer varyantlar için önerir.
  • Durum için ButtonGroup kullanmak. Seçili olanı göstermek istiyorsanız ToggleGroup kullanın; o hem görünümü hem de data-state bilgisini yönetir.
  • ToggleGroupItem'a value vermemek. Belgedeki API tablosunda value zorunlu olarak işaretlidir.
  • separator dosyasını eksik bırakmak. Elle kurulumda button-group.tsx içe aktardığı separator bileşenini de ister.

Sık Sorulan Sorular

ButtonGroup Radix kullanıyor mu?

Grubun kendisi düz bir div öğesidir. Radix yalnızca ButtonGroupText içindeki Slot ve ButtonGroupSeparator içindeki Separator üzerinden işin içine girer. Değişiklik günlüğü de Ekim 2025 bileşenlerinin Radix, Base UI ya da React Aria gibi farklı kütüphanelerle birlikte çalıştığını söyler.

ToggleGroup'u bir formda nasıl kullanırım?

Değeri value ve onValueChange ile kontrol edip form kütüphanenize bağlarsınız. Form tarafındaki kalıplar React Hook Form ve Zod yazısında anlatılıyor.

Gruptaki bir butonu diğerlerinden farklı renkte gösterebilir miyim?

Evet. ButtonGroup butonların varyantına dokunmaz; yalnızca köşe ve çerçeve birleştirmesini yapar. Bu yüzden bir gruptaki butonlara farklı variant değerleri verebilirsiniz. Belgenin ayırıcı notu burada da geçerlidir: dolu varyantları yan yana koyuyorsanız aralarına ButtonGroupSeparator eklemek sınırları belirginleştirir.

Toggle ile Switch arasındaki fark nedir?

İkisi de iki durumlu kontrollerdir. Toggle buton görünümündedir; belgedeki örneklerde kalın, italik, yer imi gibi araç çubuğu düğmeleri olarak kullanılır. Switch ise belgedeki "Airplane Mode" örneğindeki gibi bir ayarı açıp kapatan anahtar görünümündedir. Switch Checkbox, Radio Group, Switch ve Slider yazısında anlatılıyor.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: shadcn/ui öğrenme rehberi · Button varyantları · cva ile özelleştirme · Input ve Input Group · Dropdown Menu