shadcn/ui'daki Button, sıradan bir button öğesinin etrafına hazır stiller ekleyen küçük bir React bileşenidir. Altı görünüm (default, outline, secondary, ghost, destructive, link) ve altı boyut (default, sm, lg, icon, icon-sm, icon-lg) sunar; asChild özelliğiyle de bir bağlantıyı ya da başka bir bileşeni buton gibi göstermenizi sağlar. npx shadcn@latest add button komutuyla projenize components/ui/button.tsx dosyası olarak kopyalanır ve bu dosya tamamen sizindir.
Bu yazıda resmî belge sayfasını ve depodaki button.tsx kaynağını birlikte okuyarak her varyantın ve boyutun ne yaptığını, ikonlu ve yükleniyor durumundaki butonları, asChild ile link oluşturmayı ve Tailwind v4 ile gelen imleç değişikliğini anlatıyoruz. Bileşenin kaynağını düzenleyip yeni varyant eklemek isterseniz cva ile bileşen özelleştirme yazısına, butonları yan yana gruplamak için Button Group ve Toggle Group yazısına bakabilirsiniz. Serinin tamamı shadcn/ui öğrenme rehberi sayfasında.
Kurulum: CLI ya da elle
Belge sayfası iki yol gösterir. CLI yolunda tek komut yeterlidir; komut, projenizdeki components.json ayarlarına bakarak dosyayı doğru klasöre yazar ve gereken paketi kurar. pnpm, yarn ve bun kullanıyorsanız belgelerdeki diğer sekmelerde aynı komutun karşılığı vardır.
npx shadcn@latest add buttonElle kurulumda önce bileşenin tek dış bağımlılığı olan @radix-ui/react-slot paketini kurarsınız, ardından belge sayfasındaki kaynak kodu components/ui/button.tsx dosyasına yapıştırır ve içindeki import yollarını kendi proje yapınıza göre düzeltirsiniz. Kodun içinde @/lib/utils dosyasındaki cn() yardımcısı kullanıldığı için bu dosyanın da projede olması gerekir; ayrıntısı cn() fonksiyonu yazısında, sıfırdan kurulum ise manuel kurulum rehberinde.
npm install @radix-ui/react-slotKurulumdan sonra bileşeni her yerde aynı yoldan içe aktarırsınız. Belgelerdeki kullanım örneği şöyledir:
import { Button } from "@/components/ui/button"
export function KaydetButonu() {
return <Button variant="outline">Kaydet</Button>
}Varyantlar: variant değerleri

variant özelliği butonun rengini ve çerçevesini belirler. Renkler doğrudan Tailwind paletinden değil, temanın CSS değişkenlerinden gelir; örneğin bg-primary sınıfı --primary değişkenine bağlıdır. Bu yüzden temayı değiştirdiğinizde bütün butonlar kendiliğinden uyum sağlar. Değişkenlerin listesi ve yeni renk ekleme tema ve CSS değişkenleri yazısında anlatılıyor.
| variant | Kaynaktaki temel sınıflar | Görünüm |
|---|---|---|
| default | bg-primary text-primary-foreground hover:bg-primary/90 | Dolu, ana renkte buton |
| destructive | bg-destructive text-white, karanlık modda dark:bg-destructive/60 | Hata rengiyle dolu buton |
| outline | border bg-background shadow-xs hover:bg-accent | Çerçeveli, arka planı sayfa renginde |
| secondary | bg-secondary text-secondary-foreground hover:bg-secondary/80 | İkincil renkte dolu buton |
| ghost | hover:bg-accent hover:text-accent-foreground | Yalnızca üzerine gelince arka plan alır |
| link | text-primary underline-offset-4 hover:underline | Bağlantı gibi görünen buton |
<Button>Varsayılan</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">İkincil</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Sil</Button>
<Button variant="link">Bağlantı</Button>outline ve ghost varyantları karanlık modda ayrı sınıflar da taşır (dark:bg-input/30, dark:hover:bg-accent/50 gibi). Karanlık modu projeye eklemek için dark mode kurulum yazısına bakın; butonun kendisinde ek bir ayar gerekmez.
Boyutlar: size değerleri

size özelliği yüksekliği, yatay dolguyu ve ikon butonlarında kare ölçüyü belirler. Metinli boyutlarda has-[>svg] seçicisi dikkat çekicidir: butonun doğrudan çocuğu bir SVG ise yatay dolgu biraz azaltılır, böylece ikonlu butonlar dengeli görünür.
| size | Kaynaktaki sınıflar | Kullanım |
|---|---|---|
| default | h-9 px-4 py-2, ikon varsa px-3 | Standart metinli buton |
| sm | h-8 gap-1.5 px-3, ikon varsa px-2.5 | Küçük metinli buton |
| lg | h-10 px-6, ikon varsa px-4 | Büyük metinli buton |
| icon | size-9 | Yalnızca ikon, standart |
| icon-sm | size-8 | Yalnızca ikon, küçük |
| icon-lg | size-10 | Yalnızca ikon, büyük |
Belgedeki boyut örneği her metinli boyutun yanına aynı ölçekteki ikon boyutunu koyar: sm ile icon-sm, default ile icon, lg ile icon-lg aynı yüksekliktedir. Bir satırda metinli ve ikonlu butonları karıştırıyorsanız bu eşleşmeye uymak hizayı korur.
import { ArrowUpRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
export function BoyutOrnegi() {
return (
<div className="flex items-center gap-2">
<Button size="sm" variant="outline">Küçük</Button>
<Button size="icon-sm" aria-label="Gönder" variant="outline">
<ArrowUpRightIcon />
</Button>
<Button size="lg" variant="outline">Büyük</Button>
<Button size="icon-lg" aria-label="Gönder" variant="outline">
<ArrowUpRightIcon />
</Button>
</div>
)
}İkonlu ve yalnızca ikon içeren butonlar
Belgeler, ikon ile metin arasındaki boşluğun butonun boyutuna göre kendiliğinden ayarlandığını ve ikona kenar boşluğu vermenize gerek olmadığını özellikle belirtir. Bunu sağlayan, temel sınıflardaki gap-2 ve sm boyutundaki gap-1.5 değeridir. İkonu metinden önce ya da sonra koymanız yeterlidir:
import { IconGitBranch } from "@tabler/icons-react"
<Button variant="outline" size="sm">
<IconGitBranch /> Yeni Dal
</Button>Yalnızca ikon içeren butonlarda ekranda okunacak bir metin yoktur. Belgedeki bütün ikon butonu örnekleri bu nedenle aria-label özniteliği taşır; ekran okuyucular butonun adını buradan okur. Bu kural shadcn/ui'a özgü değil, genel erişilebilirlik gereğidir; ayrıntısı için ARIA rolleri ve öznitelikleri yazısına bakabilirsiniz.
import { CircleFadingArrowUpIcon } from "lucide-react"
<Button variant="outline" size="icon" aria-label="Yükselt">
<CircleFadingArrowUpIcon />
</Button>Yuvarlak bir ikon butonu için belgeler yeni bir varyant yazmak yerine rounded-full sınıfını className ile eklemeyi önerir. cn() içindeki tailwind-merge, temel sınıflardaki rounded-md ile sizin rounded-full sınıfınızın çakıştığını anlar ve yalnızca sizinkini bırakır.
<Button variant="outline" size="icon" className="rounded-full" aria-label="Aç">
<ArrowUpRightIcon />
</Button>Yükleniyor durumu: Spinner ile buton
shadcn/ui'da ayrı bir "loading" özelliği yoktur. Belgelerdeki yöntem, butonu disabled yapıp içine Spinner bileşenini koymaktır. Spinner ayrı bir bileşendir ve npx shadcn@latest add spinner ile eklenir; kaynağına bakarsanız lucide-react'in Loader2Icon ikonunu role="status", aria-label="Loading" ve animate-spin ile sarmaladığını görürsünüz. Spinner bir SVG olduğu için butonun ikon kuralları ona da uygulanır: boyutu 4 birime ayarlanır ve metinle arasına aynı boşluk girer.
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
export function GonderButonu() {
const [gonderiliyor, setGonderiliyor] = useState(false)
return (
<Button
size="sm"
variant="outline"
disabled={gonderiliyor}
onClick={() => setGonderiliyor(true)}
>
{gonderiliyor && <Spinner />}
Gönder
</Button>
)
}Bu örnek belgedeki button-loading örneğinin durum eklenmiş hâlidir: belgedeki sürüm butonu sabit olarak disabled gösterir, burada ise tıklayınca devre dışı kalıp spinner gösteriyor. Spinner'ın içindeki aria-label değeri İngilizce "Loading" olarak gelir; Türkçe bir arayüzde bunu components/ui/spinner.tsx dosyasında değiştirmeniz ya da bileşene kendi aria-label değerinizi geçirmeniz mümkündür, çünkü bileşen ek özellikleri SVG'ye aktarır. Formlarda gönderim sırasında butonu bu şekilde kilitlemenin örnekleri Server Actions ile form yazısında var.
asChild: başka bir öğeyi buton gibi göstermek
Kaynaktaki en kısa ama en çok iş gören satır şudur: const Comp = asChild ? Slot : "button". asChild verilmediğinde bileşen gerçek bir button öğesi üretir. asChild verildiğinde ise Radix'in Slot bileşeni kullanılır; Slot kendi öğesini oluşturmaz, aldığı sınıfları ve özellikleri tek çocuğuna aktarır. Sonuç olarak ekranda bir bağlantı öğesi oluşur ama buton stiliyle görünür. Belgelerdeki Next.js örneği:
import Link from "next/link"
import { Button } from "@/components/ui/button"
export function GirisBaglantisi() {
return (
<Button asChild>
<Link href="/login">Giriş yap</Link>
</Button>
)
}Bu yaklaşımın faydası anlamın doğru kalmasıdır: sayfa değiştiren bir öğe bağlantı olarak, işlem yapan bir öğe buton olarak işaretlenir ve ikisi aynı görünür. Next.js'teki Link bileşeninin kendisi için Next.js App Router yazısına bakabilirsiniz.
buttonVariants fonksiyonu da dışa aktarıldığı için bazı durumlarda asChild gerekmez. Depodaki alert-dialog.tsx, onay ve iptal düğmelerine buttonVariants() ve buttonVariants({ variant: "outline" }) sınıflarını verir; pagination.tsx ise sayfa bağlantılarını aynı fonksiyonla buton gibi biçimlendirir. Yani buton görünümünü yalnızca sınıf olarak almak istediğinizde bu fonksiyonu doğrudan çağırabilirsiniz.
Tailwind v4 ve imleç (cursor)
Tailwind CSS v4, butonlar için varsayılan imleci pointer yerine default yaptı; shadcn/ui'ın Tailwind v4 sayfası da "Buttons now use the default cursor" diyerek aynı değişikliği listeler. Yani v4 projesinde butonun üzerine geldiğinizde el imleci görmezsiniz. Eski davranışı istiyorsanız belge, global CSS dosyanıza şu kuralı eklemenizi önerir:
@layer base {
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
}Bu kural yalnızca devre dışı olmayan butonları ve role="button" taşıyan öğeleri etkiler. Tailwind v3 projesinden geçiş yapıyorsanız diğer adımlar Tailwind v4 geçişi yazısında anlatılıyor.
Eski projeye icon-sm ve icon-lg eklemek
Belge sayfasının değişiklik günlüğünde 24 Eylül 2025 tarihli bir not vardır: butona icon-sm ve icon-lg boyutları eklendi. Bu tarihten önce eklediğiniz bir button.tsx dosyasında bu boyutlar yoksa, dosya sizin olduğu için CLI onu kendiliğinden güncellemez. Belge, buttonVariants içindeki size nesnesine iki satır eklemenizi söyler:
const buttonVariants = cva("...", {
variants: {
size: {
// ...
"icon-sm": "size-8",
"icon-lg": "size-10",
// ...
},
},
})Bu satırları eklemeden size="icon-sm" yazarsanız TypeScript hata verir, çünkü size özelliğinin türü VariantProps<typeof buttonVariants> ile doğrudan bu nesneden türetilir.
Hepsi bir arada: bir formun alt çubuğu
Öğrendiklerimizi küçük bir örnekte birleştirelim. Bir düzenleme formunun altında üç işlem olsun: kaydetmek, vazgeçip listeye dönmek ve kaydı silmek. Ana işlem varsayılan varyantla, geri dönüş bağlantısı outline görünümlü bir link olarak, silme işlemi de destructive varyantla yazılır. Vazgeç bağlantısı sayfa değiştirdiği için asChild ile gerçek bir bağlantıya dönüşür; silme butonu formun içinde olduğu için type="button" alır ve form gönderimini tetiklemez.
import Link from "next/link"
import { Trash2Icon } from "lucide-react"
import { Button } from "@/components/ui/button"
export function FormAltCubugu() {
return (
<div className="flex items-center gap-2">
<Button type="submit">Kaydet</Button>
<Button variant="outline" asChild>
<Link href="/kayitlar">Vazgeç</Link>
</Button>
<Button type="button" variant="destructive" className="ml-auto">
<Trash2Icon /> Sil
</Button>
</div>
)
}Silme butonundaki ml-auto sınıfı butonu satırın sağ ucuna iter; bu, ikonun kenar boşluğu değil butonun kendi yerleşimidir, o yüzden ikon kuralına aykırı değildir. Silmeden önce onay istemek isterseniz butonu bir Alert Dialog içine tetikleyici olarak koyabilirsiniz.
API özeti
| Prop | Tür | Varsayılan |
|---|---|---|
| variant | default, outline, ghost, destructive, secondary, link | default |
| size | default, sm, lg, icon, icon-sm, icon-lg | default |
| asChild | boolean | false |
Bunların dışında onClick, type, disabled, form gibi standart button özelliklerinin hepsi doğrudan öğeye geçer. Birden fazla butonu tek bir kutuda birleştirmek için ButtonGroup kullanılır; o bileşen ayrı bir yazıda: Button Group ve Toggle Group.
Sık yapılan hatalar

- İkon butonunda aria-label unutmak. Metin olmadığı için buton ekran okuyucuda adsız kalır. Belgedeki tüm ikon örnekleri
aria-labelkullanır. - İkona kenar boşluğu vermek.
mr-2gibi sınıflar gereksizdir; boşluğu butonungapdeğeri ayarlar ve ek boşluk hizayı bozar. - Form içinde type belirtmemek.
Button,typeiçin varsayılan bir değer koymaz; özelliği doğrudan HTMLbuttonöğesine bırakır. Bir formun içindeki ve gönderim yapmaması gereken butonlaratype="button"verin. (Karşılaştırma içinInputGroupButtonkaynağındatype="button"varsayılandır.) - asChild içine birden fazla öğe koymak. Slot sınıfları tek bir çocuğa aktarır; belgedeki örneklerin hepsinde
asChildiçinde tek bir öğe vardır. İkon ve metni o tek öğenin içine yerleştirin. - Eski button.tsx ile yeni boyutları kullanmak.
icon-smveicon-lgyoksa önce yukarıdaki iki satırı ekleyin. - Tailwind v4'te imlecin değiştiğini hata sanmak. Bu bilinçli bir değişikliktir; isterseniz yukarıdaki CSS kuralıyla geri alırsınız.
Sık Sorulan Sorular
Button bileşeni bir npm paketi mi?
Hayır. CLI, bileşenin kaynak kodunu projenize dosya olarak kopyalar; npm'den kurulan tek parça @radix-ui/react-slot gibi alt bağımlılıklardır. Bu yüzden butonu istediğiniz gibi düzenleyebilirsiniz. Bu yaklaşımın mantığı shadcn/ui nedir yazısında anlatılıyor.
Yeni bir renk varyantı nasıl eklerim?
components/ui/button.tsx içindeki buttonVariants nesnesine yeni bir anahtar ekleyerek. Renk temada yoksa önce CSS değişkenini tanımlamanız gerekir. Adım adım örnek cva ile özelleştirme yazısında.
Butonu tıklanamaz ama görünür yapmak için ne kullanmalıyım?
disabled özelliğini verin. Temel sınıflardaki disabled:pointer-events-none disabled:opacity-50 butonu soluklaştırır ve fare olaylarını keser. Yükleniyor durumunda belgeler de aynı yöntemi kullanır.
Butonun stilini tek bir yerde değiştirmek için className yeterli mi?
Tek bir kullanım için evet; className ile verdiğiniz sınıflar tailwind-merge sayesinde çakışan varsayılanların yerine geçer. Aynı değişikliği her yerde istiyorsanız button.tsx dosyasını düzenlemek daha temizdir.
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 · cva ile varyant ekleme · Button Group ve Toggle Group · cn() fonksiyonu · Tema ve CSS değişkenleri