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.

bash
npx shadcn@latest add button

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

bash
npm install @radix-ui/react-slot

Kurulumdan sonra bileşeni her yerde aynı yoldan içe aktarırsınız. Belgelerdeki kullanım örneği şöyledir:

typescript
import { Button } from "@/components/ui/button"

export function KaydetButonu() {
  return <Button variant="outline">Kaydet</Button>
}

button.tsx dosyasının içinde ne var?

Bileşeni iyi kullanmanın en kısa yolu kaynağına bakmaktır, çünkü shadcn/ui'da kaynak zaten sizin projenizdedir. button.tsx iki şey dışa aktarır: Button bileşeni ve buttonVariants adlı bir cva çağrısı. buttonVariants, class-variance-authority kütüphanesiyle tanımlanmış bir sınıf üreticisidir: bir temel sınıf dizisi, variant ve size için değer tabloları ve varsayılan değerler içerir.

Temel sınıflarda birkaç önemli davranış saklıdır. inline-flex items-center justify-center gap-2 içeriği ortalar ve ikon ile metin arasına boşluk koyar. disabled:pointer-events-none disabled:opacity-50 devre dışı butonu soluklaştırır ve tıklamaları keser. [&_svg:not([class*='size-'])]:size-4 kuralı, kendi size-* sınıfı olmayan her SVG ikonu 4 birim boyuta getirir. focus-visible:ring-[3px] ile klavyeyle odaklanıldığında halka görünür, aria-invalid:border-destructive ile de geçersiz işaretlenmiş bir buton hata rengiyle çerçevelenir.

Button fonksiyonu, aldığı tüm standart button özelliklerini öğeye geçirir; üstüne data-slot="button", data-variant ve data-size öznitelikleri ekler. Bu öznitelikler, bir üst bileşenin içindeki butonları CSS ile hedeflemek için kullanılır. Son olarak sınıflar cn(buttonVariants({ variant, size, className })) ile birleştirilir; bu sayede sizin verdiğiniz className varsayılan sınıflarla çakıştığında sizinki kazanır.

Varyantlar: variant değerleri

Varyantlar: variant değerleri: default, destructive, outline, secondary

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.

variantKaynaktaki temel sınıflarGörünüm
defaultbg-primary text-primary-foreground hover:bg-primary/90Dolu, ana renkte buton
destructivebg-destructive text-white, karanlık modda dark:bg-destructive/60Hata rengiyle dolu buton
outlineborder bg-background shadow-xs hover:bg-accentÇerçeveli, arka planı sayfa renginde
secondarybg-secondary text-secondary-foreground hover:bg-secondary/80İkincil renkte dolu buton
ghosthover:bg-accent hover:text-accent-foregroundYalnızca üzerine gelince arka plan alır
linktext-primary underline-offset-4 hover:underlineBağlantı gibi görünen buton
typescript
<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

Boyutlar: size değerleri: default, h-8 gap-1.5 px-3, ikon varsa px-2.5, h-10 px-6, ikon varsa px-4, icon

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.

sizeKaynaktaki sınıflarKullanım
defaulth-9 px-4 py-2, ikon varsa px-3Standart metinli buton
smh-8 gap-1.5 px-3, ikon varsa px-2.5Küçük metinli buton
lgh-10 px-6, ikon varsa px-4Büyük metinli buton
iconsize-9Yalnızca ikon, standart
icon-smsize-8Yalnızca ikon, küçük
icon-lgsize-10Yalnı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.

typescript
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:

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

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

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

typescript
"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:

typescript
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:

css
@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:

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

typescript
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

PropTürVarsayılan
variantdefault, outline, ghost, destructive, secondary, linkdefault
sizedefault, sm, lg, icon, icon-sm, icon-lgdefault
asChildbooleanfalse

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

Sık yapılan hatalar: İkon butonunda aria-label unutmak., İkona kenar boşluğu vermek., Form içinde type belirtmemek., asChild içine birden fazla öğe koymak.
  • İkon butonunda aria-label unutmak. Metin olmadığı için buton ekran okuyucuda adsız kalır. Belgedeki tüm ikon örnekleri aria-label kullanır.
  • İkona kenar boşluğu vermek. mr-2 gibi sınıflar gereksizdir; boşluğu butonun gap değeri ayarlar ve ek boşluk hizayı bozar.
  • Form içinde type belirtmemek. Button, type için varsayılan bir değer koymaz; özelliği doğrudan HTML button öğesine bırakır. Bir formun içindeki ve gönderim yapmaması gereken butonlara type="button" verin. (Karşılaştırma için InputGroupButton kaynağında type="button" varsayılandır.)
  • asChild içine birden fazla öğe koymak. Slot sınıfları tek bir çocuğa aktarır; belgedeki örneklerin hepsinde asChild içinde tek bir öğe vardır. İkon ve metni o tek öğenin içine yerleştirin.
  • Eski button.tsx ile yeni boyutları kullanmak. icon-sm ve icon-lg yoksa ö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