shadcn/ui'da seçim yapılan dört temel kontrol vardır: işaretlenip kaldırılabilen Checkbox, aynı anda yalnızca bir seçeneğin seçilebildiği RadioGroup, bir ayarı açıp kapatan Switch ve bir aralıktan değer seçtiren Slider. Dördü de Radix UI ilkelleri üzerine kuruludur, npx shadcn@latest add ile projenize eklenir ve hem kontrolsüz (defaultChecked, defaultValue) hem kontrollü (checked ile onCheckedChange, value ile onValueChange) kullanılabilir.
Bu bileşenlerin belge sayfaları kısadır; çoğu yalnızca kurulum ve tek satırlık kullanım içerir. Bu yüzden yazıda belge sayfalarını, depodaki örnek dosyaları, bileşenlerin kaynak kodunu ve React Hook Form sayfasındaki alan örneklerini birlikte kullandık. Her bileşenin altında etiket bağlama, kontrollü kullanım ve hata gösterimi var; sonunda bir karşılaştırma tablosu ve sık yapılan hatalar bulunuyor. Etiket bileşeninin kendisi Input ve Label yazısında anlatılıyor; serinin tamamı shadcn/ui öğrenme rehberi sayfasında.
Ortak noktalar: Radix, data-state ve Label

Dört bileşenin kaynağı aynı kalıbı izler. Her biri ilgili Radix ilkelini (@radix-ui/react-checkbox, react-radio-group, react-switch, react-slider) sarar, üstüne sınıflar ve bir data-slot özniteliği ekler, geri kalan özellikleri olduğu gibi Radix'e geçirir. Bu yüzden belgede yazmayan bir özellik arıyorsanız (örneğin name ya da required) bakmanız gereken yer, belge sayfasının üstünde bağlantısı verilen Radix API sayfasıdır.
Görsel durum data-state özniteliğinden okunur: işaretli bir checkbox ya da açık bir switch data-state="checked" taşır, kaynaktaki data-[state=checked]:bg-primary gibi sınıflar da rengi buna göre değiştirir. Checkbox ve Switch ayrıca peer sınıfı taşır; böylece hemen ardından gelen Label, kontrol devre dışıysa peer-disabled kurallarıyla soluklaşır. Etiketi bağlamak için her kontrole bir id, etikete de aynı değerle htmlFor verirsiniz.
Bu dosyaların hepsi "use client" satırıyla başlar. Next.js App Router'da bileşeni doğrudan bir sunucu bileşeninde kullanabilirsiniz; ancak useState ile kontrollü kullanım yazdığınız kendi bileşeninizin de istemci bileşeni olması gerekir. Bu ayrımın ayrıntısı Next.js kurulum yazısında.
Checkbox
Kurulum npx shadcn@latest add checkbox, elle kurulumda gereken paket @radix-ui/react-checkbox'tır. Belgedeki tanım, kullanıcının işaretli ve işaretsiz arasında geçiş yapmasını sağlayan bir kontroldür. Kaynakta kutu size-4 boyutunda ve rounded-[4px] köşelidir; işaretlendiğinde içinde lucide-react'in CheckIcon ikonu görünür.
npx shadcn@latest add checkbox"use client"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
export function SozlesmeOnayi() {
return (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
<Checkbox id="terms" />
<Label htmlFor="terms">Kullanım koşullarını kabul ediyorum</Label>
</div>
<div className="flex items-start gap-3">
<Checkbox id="terms-2" defaultChecked />
<div className="grid gap-2">
<Label htmlFor="terms-2">Bülten almak istiyorum</Label>
<p className="text-muted-foreground text-sm">
İstediğiniz zaman abonelikten çıkabilirsiniz.
</p>
</div>
</div>
<div className="flex items-start gap-3">
<Checkbox id="toggle" disabled />
<Label htmlFor="toggle">Bildirimleri aç</Label>
</div>
</div>
)
}Bu örnek belgedeki checkbox-demo dosyasının Türkçeleştirilmiş hâlidir. Üç durumu gösterir: düz checkbox, defaultChecked ile başlangıçta işaretli ve açıklama metinli checkbox, disabled ile devre dışı checkbox. Aynı dosyanın son örneği daha da ilginçtir: tüm satırı bir Label ile sarar ve has-[[aria-checked=true]]:border-blue-600 gibi sınıflarla, içindeki kutu işaretlendiğinde bütün kartın çerçevesini ve arka planını değiştirir. Radix checkbox'ı bir buton olarak üretip aria-checked özniteliğini güncellediği için bu seçici çalışır.
Kontrollü checkbox ve checkbox listesi
Kontrollü kullanımda checked ve onCheckedChange verilir; dikkat edin, olay adı onChange değildir. React Hook Form sayfasındaki örnek, birden çok checkbox'ı tek bir dizi değerine bağlar: her kutu, kendi kimliği dizide varsa işaretli görünür; değiştiğinde de kimlik diziye eklenir ya da diziden çıkarılır. Aşağıdaki sürüm aynı mantığı form kütüphanesi olmadan useState ile kurar:
"use client"
import { useState } from "react"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
const gorevler = [
{ id: "push", label: "Anlık bildirim" },
{ id: "email", label: "E-posta" },
]
export function BildirimTercihleri() {
const [secili, setSecili] = useState<string[]>(["email"])
return (
<div className="flex flex-col gap-3">
{gorevler.map((gorev) => (
<div key={gorev.id} className="flex items-center gap-3">
<Checkbox
id={`bildirim-${gorev.id}`}
checked={secili.includes(gorev.id)}
onCheckedChange={(checked) => {
setSecili(
checked
? [...secili, gorev.id]
: secili.filter((value) => value !== gorev.id)
)
}}
/>
<Label htmlFor={`bildirim-${gorev.id}`}>{gorev.label}</Label>
</div>
))}
</div>
)
}checked özelliği yalnızca true ve false almaz. Data Table belgesindeki "tümünü seç" kutusu, bazı satırlar seçiliyken "indeterminate" değerini verir ve onCheckedChange içinde gelen değeri !!value ile mantıksal değere çevirir. Bu, geri çağırma fonksiyonunun her zaman saf bir boolean almayabileceğini hatırlatır. Tablo örneğinin tamamı Data Table satır seçimi yazısında.
Radio Group
Kurulum npx shadcn@latest add radio-group, elle kurulum paketi @radix-ui/react-radio-group'tır. Belge onu, aynı anda en fazla birinin işaretlenebildiği butonlar kümesi olarak tanımlar. İki parçası vardır: kapsayıcı RadioGroup ve her seçenek için RadioGroupItem. Seçili değer grup düzeyinde tutulur; her öğe kendi value değerini taşır.
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export function YogunlukSecimi() {
return (
<RadioGroup defaultValue="comfortable">
<div className="flex items-center gap-3">
<RadioGroupItem value="default" id="r1" />
<Label htmlFor="r1">Varsayılan</Label>
</div>
<div className="flex items-center gap-3">
<RadioGroupItem value="comfortable" id="r2" />
<Label htmlFor="r2">Rahat</Label>
</div>
<div className="flex items-center gap-3">
<RadioGroupItem value="compact" id="r3" />
<Label htmlFor="r3">Sıkı</Label>
</div>
</RadioGroup>
)
}Kaynakta RadioGroup varsayılan olarak grid gap-3 sınıflarını alır, bu yüzden seçenekler alt alta 3 birim aralıkla dizilir; yan yana istiyorsanız className ile düzeni değiştirebilirsiniz. Seçili öğenin içinde küçük, dolu bir daire (CircleIcon) görünür. Kontrollü kullanımda React Hook Form sayfasındaki gibi value ve onValueChange gruba verilir. Aynı sayfadaki plan seçimi örneği, her seçeneği bir FieldLabel içine sararak tıklanabilir kartlar oluşturur ve hata durumunda aria-invalid özniteliğini her RadioGroupItem'a verir. Kart görünümlü seçenekler için Field bileşenindeki choice card yapısına bakabilirsiniz.
<RadioGroup defaultValue="monthly" className="flex gap-6">
<div className="flex items-center gap-2">
<RadioGroupItem value="monthly" id="odeme-aylik" />
<Label htmlFor="odeme-aylik">Aylık</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="yearly" id="odeme-yillik" />
<Label htmlFor="odeme-yillik">Yıllık</Label>
</div>
</RadioGroup>Bu kısa örnekte className="flex gap-6", kaynaktaki grid gap-3 düzenini ezer ve iki seçeneği yan yana dizer. tailwind-merge grid ile flex sınıflarının aynı CSS özelliğini (display) ayarladığını bildiği için yalnızca sonuncusunu tutar.
Switch

Kurulum npx shadcn@latest add switch, elle kurulum paketi @radix-ui/react-switch'tir. Switch, checkbox ile aynı checked ve onCheckedChange özelliklerini kullanır ama görünümü kaydırmalı bir anahtardır. Kaynakta kapalı durumda arka plan bg-input, açık durumda bg-primary olur; içteki yuvarlak (SwitchPrimitive.Thumb) açıldığında translate-x ile sağa kayar.
"use client"
import { useState } from "react"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
export function UcakModu() {
const [acik, setAcik] = useState(false)
return (
<div className="flex items-center gap-2">
<Switch id="airplane-mode" checked={acik} onCheckedChange={setAcik} />
<Label htmlFor="airplane-mode">
Uçak modu {acik ? "açık" : "kapalı"}
</Label>
</div>
)
}Belgedeki temel örnek kontrolsüzdür: yalnızca Switch id="airplane-mode" ve ona bağlı bir Label içerir. Yukarıdaki sürüm duruma bağlanmış hâlidir. React Hook Form örneğinde de checked={field.value} ve onCheckedChange={field.onChange} yazılır; hata gösterimi için aria-invalid switch'e, data-invalid ise onu saran Field bileşenine verilir.
Slider
Kurulum npx shadcn@latest add slider, elle kurulum paketi @radix-ui/react-slider'dır. Belgedeki kullanım satırı Slider'ın en önemli özelliğini gösterir: değer bir dizidir.
import { Slider } from "@/components/ui/slider"
<Slider defaultValue={[33]} max={100} step={1} />Dizinin nedeni, Slider'ın birden fazla tutamaç (thumb) taşıyabilmesidir. Kaynak kod, value ya da defaultValue dizisinin uzunluğu kadar tutamaç çizer; ikisi de verilmemişse [min, max] dizisini kullandığı için iki tutamaçlı bir aralık oluşur. min için varsayılan 0, max için 100'dür. İki değerli bir dizi verdiğinizde fiyat aralığı gibi bir alt ve üst sınır seçtirebilirsiniz. Depodaki field-slider örneği bunu kontrollü olarak yapar:
"use client"
import { useState } from "react"
import { Slider } from "@/components/ui/slider"
export function FiyatAraligi() {
const [value, setValue] = useState([200, 800])
return (
<div className="grid w-full max-w-md gap-2">
<p className="text-sm">
Bütçe: {value[0]} - {value[1]} TL
</p>
<Slider
value={value}
onValueChange={setValue}
max={1000}
min={0}
step={10}
aria-label="Fiyat aralığı"
/>
</div>
)
}Burada step={10} değerin onar onar değişmesini sağlar; aria-label ise görünür bir etiketi olmayan kaydırıcıya erişilebilir bir ad verir. Kaynakta dikey yön için de sınıflar bulunur (data-[orientation=vertical]:h-full, min-h-44 gibi); yönü değiştiren özellik Radix Slider API'sinde tanımlıdır. Devre dışı bir kaydırıcı data-[disabled]:opacity-50 ile soluklaşır.
Görünümü değiştirmek: data-state sınıfları
Bu kontrollerin renklerini değiştirmenin en kısa yolu, belgedeki checkbox kartı örneğinin yaptığı gibi data-[state=checked]: önekli sınıfları className ile vermektir. O örnekte kutu işaretlendiğinde data-[state=checked]:bg-blue-600 ve data-[state=checked]:border-blue-600 ile maviye döner, karanlık modda da dark:data-[state=checked]:bg-blue-700 kullanılır. cn() içindeki tailwind-merge, sizin bg-* sınıfınızla kaynaktaki bg-primary sınıfının aynı durum önekine sahip olduğunu görür ve sizinkini bırakır. Aynı yöntem Switch için de geçerlidir; kaynakta açık durum data-[state=checked]:bg-primary, kapalı durum data-[state=unchecked]:bg-input sınıfıyla boyanır. Değişikliğin her yerde geçerli olmasını istiyorsanız sınıfı components/ui/checkbox.tsx dosyasında değiştirmek daha temizdir; bu yaklaşım bileşen özelleştirme yazısında anlatılıyor.
Mini örnek: bildirim ayarları paneli
Dört kontrolü tek bir ayar panelinde birleştirelim. Bildirimlerin tamamı bir Switch ile açılıp kapanır; hangi kanalların kullanılacağı bir checkbox listesinden, özetin ne sıklıkla geleceği bir radio grubundan, bildirimlerin hangi saatler arasında gelebileceği de iki tutamaçlı bir Slider'dan seçilir. Switch kapalıyken diğer kontroller disabled olur.
"use client"
import { useState } from "react"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Slider } from "@/components/ui/slider"
import { Switch } from "@/components/ui/switch"
export function BildirimAyarlari() {
const [acik, setAcik] = useState(true)
const [eposta, setEposta] = useState(true)
const [siklik, setSiklik] = useState("daily")
const [saatler, setSaatler] = useState([8, 18])
return (
<div className="grid max-w-md gap-6">
<div className="flex items-center justify-between gap-3">
<Label htmlFor="bildirim-acik">Bildirimler</Label>
<Switch id="bildirim-acik" checked={acik} onCheckedChange={setAcik} />
</div>
<div className="flex items-center gap-3">
<Checkbox
id="kanal-eposta"
checked={eposta}
onCheckedChange={(value) => setEposta(!!value)}
disabled={!acik}
/>
<Label htmlFor="kanal-eposta">E-posta ile gönder</Label>
</div>
<RadioGroup value={siklik} onValueChange={setSiklik}>
<div className="flex items-center gap-3">
<RadioGroupItem value="daily" id="siklik-gunluk" disabled={!acik} />
<Label htmlFor="siklik-gunluk">Günlük özet</Label>
</div>
<div className="flex items-center gap-3">
<RadioGroupItem value="weekly" id="siklik-haftalik" disabled={!acik} />
<Label htmlFor="siklik-haftalik">Haftalık özet</Label>
</div>
</RadioGroup>
<div className="grid gap-2">
<p className="text-sm">
Bildirim saatleri: {saatler[0]} - {saatler[1]}
</p>
<Slider
value={saatler}
onValueChange={setSaatler}
min={0}
max={23}
step={1}
disabled={!acik}
aria-label="Bildirim saatleri"
/>
</div>
</div>
)
}Örnekte dikkat edilecek birkaç nokta var. Checkbox'ın geri çağırması, tablo örneğindeki gibi !!value ile mantıksal değere çevrilir. Checkbox'ın etiketi, kutu devre dışıyken peer-disabled kuralı sayesinde kendiliğinden soluklaşır. Radio seçeneklerinde devre dışı bırakma her RadioGroupItem'a ayrı ayrı verildi; kaynakta öğe sınıflarında disabled:opacity-50 bulunduğu için soluklaşma kendiliğinden gelir. Slider'da iki değerli dizi iki tutamaç üretir; min={0} ve max={23} ile değerler günün saatleriyle sınırlanır, step={1} ile de tam saatlere oturur. Her kontrolün değeri ayrı bir useState içinde tutulduğu için paneldeki metinler anında güncellenir; aynı değerleri bir form kütüphanesine bağlamak isterseniz her useState çiftinin yerini bir Controller alır.
Kontrolsüz ve kontrollü kullanım özeti
| Bileşen | Başlangıç değeri (kontrolsüz) | Kontrollü değer | Değişiklik olayı |
|---|---|---|---|
| Checkbox | defaultChecked | checked (true, false veya indeterminate) | onCheckedChange |
| Switch | defaultChecked | checked | onCheckedChange |
| RadioGroup | defaultValue (string) | value (string) | onValueChange |
| Slider | defaultValue (sayı dizisi) | value (sayı dizisi) | onValueChange |
Kontrolsüz kullanım, değeri yalnızca form gönderilirken okuyacağınız basit durumlar için yeterlidir. Ekranda başka bir şeyi bu değere göre değiştirecekseniz (yukarıdaki "açık / kapalı" metni ya da fiyat aralığı gibi) kontrollü kullanım gerekir. Form kütüphaneleriyle bağlantı için React Hook Form ve TanStack Form yazılarına bakabilirsiniz.
Hata durumu
Checkbox, RadioGroupItem ve Switch kaynaklarında aria-invalid:border-destructive ve aria-invalid:ring-destructive/20 sınıfları vardır. React Hook Form sayfası, doğrulama başarısız olduğunda bu kontrollere aria-invalid={fieldState.invalid}, onları saran Field bileşenine de data-invalid vermeyi önerir. Checkbox listelerinde ayrıca FieldGroup bileşenine data-slot="checkbox-group" eklenmesi, doğru aralık ve stil için hatırlatılır.
Sık yapılan hatalar

- onChange beklemek. Bu bileşenler Radix olaylarını kullanır: Checkbox ve Switch için
onCheckedChange, RadioGroup ve Slider içinonValueChange. - Slider'a tek sayı vermek.
defaultValue={50}yerinedefaultValue={[50]}yazılmalıdır; kaynak, tutamaç sayısını dizinin uzunluğundan hesaplar. - Slider'a hiç değer vermemek. Bu durumda kaynak
[min, max]kullanır ve beklemediğiniz şekilde iki tutamaç çizilir. - id ve htmlFor'u eşleştirmemek. Etiket kontrole bağlanmaz; etikete tıklamak kutuyu işaretlemez ve ekran okuyucu adı okuyamaz.
- RadioGroupItem'ları aynı id ile çoğaltmak. Liste
mapile üretiliyorsa her öğeye benzersiz biridverin (belgedeki örnekler değere dayalı id kullanır). - onCheckedChange değerini doğrudan boolean saymak. Checkbox
"indeterminate"durumunu da taşıyabildiği için belgedeki tablo örneği!!valuekullanır.
Sık Sorulan Sorular
Checkbox mı, Switch mi kullanmalıyım?
Belgeler bu konuda bir kural koymaz. Örneklerde Checkbox koşulları kabul etme ve listeden birden çok seçim yapma için, Switch ise uçak modu ya da çok adımlı doğrulama gibi bir ayarı açıp kapatmak için kullanılır. Kodda ikisinin API'si aynıdır (checked, onCheckedChange).
Etiketi kontrolün solunda mı sağında mı göstermeliyim?
Belgelerdeki örnekler iki düzeni de kullanır. Checkbox, Radio ve Switch demolarında kontrol solda, etiket sağdadır. React Hook Form sayfasındaki Switch örneğinde ise Field orientation="horizontal" içinde başlık ve açıklama solda, anahtar sağda durur; bu düzen açıklama metni uzun olan ayarlar için daha okunaklıdır. Hangisini seçerseniz seçin, htmlFor ve id eşleşmesi etiketin tıklanabilir kalmasını sağlar.
Radio Group yerine Select ne zaman tercih edilir?
Seçenekler az ve hepsinin görünmesi faydalıysa radio grubu, seçenekler çoksa ve yer kısıtlıysa açılır bir liste daha uygundur. Açılır liste bileşenleri Select ve Native Select yazısında anlatılıyor.
Slider değerini metin olarak nasıl gösteririm?
Kontrollü kullanıp value dizisini ekrana yazdırırsınız. Depodaki örnek fiyat aralığının iki ucunu tabular-nums sınıfıyla, açıklama metninin içinde gösterir.
Bu bileşenler Base UI ile de gelir mi?
Bu yazı, depodaki new-york-v4 stilini, yani Radix tabanlı sürümü anlatıyor. Aralık 2025'te gelen npx shadcn create ile Radix ya da Base UI seçimi yapılabildiğini değişiklik günlüğü belirtir; ayrıntısı yenilikler yazısında.
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 · Input ve Label · Field bileşeni · React Hook Form ve Zod · Select ve Native Select