shadcn/ui’de durum ve yükleniyor bilgisini göstermek için beş küçük bileşen kullanılır: dikkat çeken mesaj kutusu Alert, kısa etiket ya da sayaç için Badge, tamamlanma oranını gösteren çubuk Progress, içerik yüklenirken yer tutan Skeleton ve dönen yükleniyor ikonu Spinner. Her biri tek komutla eklenir ve kaynak kodu birkaç satırdır; bu yüzden bu yazıda her bileşen için belgedeki kullanımı, kaynak koddaki varyantları ve resmî örneklerdeki birleşimleri birlikte ele alıyoruz.
Geçici bildirimler (toast) bu yazının konusu değildir; onlar için Sonner yazısına bakın. Boş durum ve liste öğesi gibi Ekim 2025’te eklenen diğer yerleşim bileşenleri Item, Empty, Kbd ve Avatar yazısında. Serinin tamamı shadcn/ui öğrenme rehberi sayfasında.
| Bileşen | Kurulum | Dayandığı yapı | Ne zaman |
|---|---|---|---|
| Alert | npx shadcn@latest add alert | div, role=alert, cva varyantı | Sayfada kalan uyarı ya da bilgi |
| Badge | npx shadcn@latest add badge | span ya da Slot, cva varyantı | Durum etiketi, sayaç |
| Progress | npx shadcn@latest add progress | Radix Progress | Yüzdesi bilinen işlem |
| Skeleton | npx shadcn@latest add skeleton | Tek div, animate-pulse | İçeriğin şekli bilinen yükleme |
| Spinner | npx shadcn@latest add spinner | lucide Loader2Icon, animate-spin | Süresi bilinmeyen kısa bekleme |
Alert: dikkat çeken mesaj kutusu
Belge Alert’i kullanıcının dikkatini çeken bir “callout” olarak tanımlar. Üç parçadan oluşur: kapsayıcı Alert, başlık AlertTitle ve açıklama AlertDescription. Kaynak kodda kapsayıcının role="alert" özniteliğiyle çizildiği görülür; bu, yardımcı teknolojilere kutunun önemli bir mesaj taşıdığını bildirir. Rol kavramı için ARIA yazısına bakabilirsiniz.
İki varyant vardır: default ve destructive. Varsayılan varyant kart rengini (bg-card text-card-foreground) kullanır; destructive ise metni ve ikonu destructive rengine boyar, açıklamayı da bu rengin biraz soluk tonuyla gösterir. Kutunun ilk çocuğu bir SVG ikon olduğunda düzen kendiliğinden iki sütunlu ızgaraya geçer: ikon solda, başlık ve açıklama sağda. İkon yoksa ilk sütun sıfır genişlikte kalır. Bu davranış kaynak koddaki has-[>svg] seçicisinden gelir; yani ikon eklemek için ayrı bir prop gerekmez, ikonu doğrudan Alert içine ilk öğe olarak koyarsınız.
import { AlertCircleIcon, CheckCircle2Icon } from "lucide-react"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
export function OdemeMesajlari() {
return (
<div className="grid w-full max-w-xl items-start gap-4">
<Alert>
<CheckCircle2Icon />
<AlertTitle>Değişiklikleriniz kaydedildi</AlertTitle>
<AlertDescription>
İkon, başlık ve açıklama içeren bir uyarı kutusu.
</AlertDescription>
</Alert>
<Alert variant="destructive">
<AlertCircleIcon />
<AlertTitle>Ödemeniz işlenemedi.</AlertTitle>
<AlertDescription>
<p>Fatura bilgilerinizi kontrol edip tekrar deneyin.</p>
<ul className="list-inside list-disc text-sm">
<li>Kart bilgilerinizi kontrol edin</li>
<li>Yeterli bakiye olduğundan emin olun</li>
<li>Fatura adresini doğrulayın</li>
</ul>
</AlertDescription>
</Alert>
</div>
)
}Bu örnek resmî alert-demo dosyasının iki kutusunu Türkçeleştirir. AlertDescription bir ızgara olarak çizildiği için içine paragraf ve liste gibi birden fazla öğe koyabilirsiniz. AlertTitle ise kaynakta line-clamp-1 sınıfı taşır: başlık tek satırla sınırlıdır, uzun başlıklar kesilir. Uzun bir mesajınız varsa onu başlığa değil açıklamaya yazın. Resmî demoda yalnızca başlık ve ikondan oluşan, açıklamasız bir Alert örneği de vardır.
Badge: etiket ve sayaç

Badge, belgedeki tanımıyla bir rozet ya da rozet gibi görünen bir bileşendir. Dört varyantı vardır: default, secondary, destructive ve outline. Kaynak kodda varsayılan olarak bir span çizilir; köşeleri tamamen yuvarlaktır (rounded-full), metin küçük ve kalındır, içine konan SVG ikonlar size-3 boyutuna getirilir.
| Varyant | Görünüm (kaynak koddan) |
|---|---|
| default | primary arka plan, primary-foreground metin |
| secondary | secondary arka plan, secondary-foreground metin |
| destructive | destructive arka plan, beyaz metin; koyu temada daha soluk arka plan |
| outline | Arka plansız, kenarlıklı, foreground metin |
Badge’in en kullanışlı özelliği asChild prop’udur. Belge bunu bir bağlantıyı rozet gibi göstermek için kullanır: asChild verildiğinde Badge kendi span öğesini çizmez, stilini Radix Slot aracılığıyla içindeki öğeye aktarır. Kaynak koddaki [a&]:hover sınıfları da yalnızca rozet bir bağlantı olduğunda çalışan üzerine gelme renklerini tanımlar. asChild ve Slot mantığının ayrıntısı cva ile bileşen özelleştirme yazısında.
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
export function RozetBaglanti() {
return (
<Badge asChild>
<Link href="/">Rozet</Link>
</Badge>
)
}Resmî badge-demo dosyası, varyantların dışında iki kalıp daha gösterir. Birincisi renk özelleştirmesi: className="bg-blue-500 text-white dark:bg-blue-600" ile varyantın rengini ezip ikonlu bir “Verified” rozeti üretmek. İkincisi sayaç: h-5 min-w-5 rounded-full px-1 font-mono tabular-nums sınıflarıyla “8”, “99”, “20+” gibi sayıları yuvarlak, sabit genişlikli bir rozet içinde göstermek. className değeri varyant sınıflarından sonra cn() ile birleştirildiği için bu ezme işlemi beklendiği gibi çalışır; nedeni cn() fonksiyonu yazısında anlatılıyor.
<Badge className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">
8
</Badge>
<Badge
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="destructive"
>
99
</Badge>Progress: ilerleme çubuğu
Progress, bir işin tamamlanma durumunu genellikle bir çubukla gösteren bileşendir ve Radix UI’nin Progress ilkeline dayanır. Elle kurulumda @radix-ui/react-progress paketi gerekir. Kullanımı tek prop’tur: value.
import { Progress } from "@/components/ui/progress"
<Progress value={33} />Kaynak kod, çubuğun nasıl çizildiğini açıkça gösterir. Dış öğe bg-primary/20 rengiyle h-2 yüksekliğinde, yuvarlak köşeli bir izdir. İçteki gösterge tam genişliktedir ve translateX(-${100 - (value || 0)}%) dönüşümüyle sola kaydırılır. Örneğin value 33 ise gösterge yüzde 67 sola kayar ve izin yalnızca üçte biri dolu görünür. value verilmezse 0 kabul edilir. Göstergede transition-all sınıfı olduğu için değer değiştiğinde çubuk yumuşakça ilerler.
Bu hesap, değerin 0 ile 100 arasında bir yüzde olarak verildiğini varsayar. İşleminiz “1000 dosyadan 129’u” gibi bir sayı üretiyorsa, onu önce yüzdeye çevirip value değerine öyle vermeniz gerekir. Resmî demo, değeri bir state içinde tutar ve yarım saniye sonra 13’ten 66’ya çıkarır:
"use client"
import * as React from "react"
import { Progress } from "@/components/ui/progress"
export function YuklemeCubugu() {
const [progress, setProgress] = React.useState(13)
React.useEffect(() => {
const timer = setTimeout(() => setProgress(66), 500)
return () => clearTimeout(timer)
}, [])
return <Progress value={progress} className="w-[60%]" />
}Bileşen dosyası "use client" ile başlar ve className kabul eder; genişliği burada w-[60%] ile sınırlıyoruz. Göstergenin rengi bg-primary olduğu için, çubuğun rengini değiştirmenin en temiz yolu temadaki --primary değişkenidir ya da bileşen dosyasını düzenlemektir.
Skeleton: yer tutucu
Skeleton, içerik yüklenirken onun yerini tutan bir yer tutucudur. Kaynak kodu tek satırlık bir div’dir: bg-accent animate-pulse rounded-md. Boyutu ve şekli tamamen sizin vereceğiniz sınıflara bağlıdır; bileşenin kendi genişliği ya da yüksekliği yoktur. Belgedeki temel kullanım bir çubuk şeklidir:
import { Skeleton } from "@/components/ui/skeleton"
<Skeleton className="h-[20px] w-[100px] rounded-full" />Belgenin “Card” örneği, yüklenen içeriğin şeklini taklit etmeyi gösterir: üstte büyük bir görsel alanı, altında iki metin satırı. İyi bir iskelet, gelecek içerikle aynı boyutlarda olur; içerik yüklendiğinde sayfa zıplamaz.
import { Skeleton } from "@/components/ui/skeleton"
export function KartIskeleti() {
return (
<div className="flex flex-col space-y-3">
<Skeleton className="h-[125px] w-[250px] rounded-xl" />
<div className="space-y-2">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
</div>
</div>
)
}Skeleton görsel bir yer tutucudur ve kaynak kodunda herhangi bir rol ya da erişilebilir etiket yoktur. Yükleme durumunu ekran okuyuculara da bildirmek istiyorsanız bunu çevreleyen yapıda kendiniz sağlamanız gerekir; aşağıdaki Spinner, kaynakta role="status" taşıdığı için bu açıdan farklıdır.
Spinner: yükleniyor göstergesi

Spinner, Ekim 2025 sürüm notlarında eklenen yeni bileşenlerden biridir. Kaynak kodu lucide-react’in Loader2Icon ikonunu size-4 animate-spin sınıflarıyla döndürür ve ikona role="status" ile aria-label="Loading" öznitelikleri ekler. Bileşen bir SVG olduğu için SVG’nin tüm prop’larını kabul eder; belgedeki API tablosunda yalnızca className listelenir.
import { Spinner } from "@/components/ui/spinner"
<Spinner />Boyut ve renk, Tailwind sınıflarıyla değiştirilir. Belge boyut için size-*, renk için text-* sınıflarını önerir; örneğin <Spinner className="size-6 text-blue-500" />. aria-label değeri kaynakta İngilizce “Loading” olarak sabittir. Türkçe bir arayüzde bunu bileşen dosyasında “Yükleniyor” olarak değiştirebilirsiniz. Kaynakta aria-label özniteliği {...props} aktarımından önce yazıldığı için, kullanım yerinde aria-label="Yükleniyor" vererek de ezebilirsiniz.
İkonu değiştirmek
Belgenin “Customization” bölümü, varsayılan ikonu başka bir ikonla değiştirmek için Spinner bileşenini düzenlemeyi gösterir. components/ui/spinner.tsx dosyasında Loader2Icon yerine örneğin LoaderIcon kullanılır, geri kalan her şey aynı kalır:
import { LoaderIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return (
<LoaderIcon
role="status"
aria-label="Yükleniyor"
className={cn("size-4 animate-spin", className)}
{...props}
/>
)
}
export { Spinner }Spinner’ı diğer bileşenlerle birleştirmek
Belge, Spinner’ın Button, Badge, Input Group, Empty ve Item içinde nasıl kullanılacağını ayrı örneklerle gösterir. En sık ihtiyaç butondur: belgeye göre spinner ile metin arasındaki boşluğu Button kendisi ayarlar, yani ek sınıf gerekmez. Resmî örnekte buton ayrıca disabled yapılır; işlem sürerken kullanıcının ikinci kez tıklaması böylece engellenir.
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
export function YukleniyorDurumlari() {
return (
<div className="flex flex-col items-center gap-4">
<Button disabled size="sm">
<Spinner />
Yükleniyor...
</Button>
<Button variant="outline" disabled size="sm">
<Spinner />
Lütfen bekleyin
</Button>
<Badge variant="secondary">
<Spinner />
Güncelleniyor
</Badge>
</div>
)
}Input Group örneğinde spinner InputGroupAddon içine, Item örneğinde ItemMedia içine konur. Item örneği ayrıca bir Progress çubuğunu ItemFooter içinde kullanarak “129 MB / 1000 MB” gibi bir indirme durumunu gösterir; Spinner ile Progress’in birlikte kullanıldığı güzel bir örnektir. Input Group için Input ve Input Group yazısına, buton yükleniyor durumu için Button yazısına bakabilirsiniz.
Küçük bir örnek: üç durumlu sipariş kartı
Bileşenlerin birlikte nasıl çalıştığını görmek için, bir siparişi yükleyen bir bileşen düşünelim. Üç durum vardır: veri gelirken iskelet, hata olursa yıkıcı (destructive) bir Alert, veri gelince durum rozetli bir satır. Aşağıdaki kod yalnızca görünüm katmanını gösterir; durum ve siparis değerlerinin sizin veri getirme kodunuzdan geldiği varsayılır.
import { AlertCircleIcon } from "lucide-react"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Badge } from "@/components/ui/badge"
import { Skeleton } from "@/components/ui/skeleton"
type Props = {
durum: "yukleniyor" | "hata" | "hazir"
siparis?: { no: string; odendi: boolean }
}
export function SiparisKarti({ durum, siparis }: Props) {
if (durum === "yukleniyor") {
return (
<div className="space-y-2">
<Skeleton className="h-4 w-[200px]" />
<Skeleton className="h-4 w-[120px]" />
</div>
)
}
if (durum === "hata" || !siparis) {
return (
<Alert variant="destructive">
<AlertCircleIcon />
<AlertTitle>Sipariş yüklenemedi</AlertTitle>
<AlertDescription>Sayfayı yenileyip tekrar deneyin.</AlertDescription>
</Alert>
)
}
return (
<div className="flex items-center gap-2">
<span className="font-medium">{siparis.no}</span>
<Badge variant={siparis.odendi ? "default" : "outline"}>
{siparis.odendi ? "Ödendi" : "Bekliyor"}
</Badge>
</div>
)
}İskeletin iki satırı, hazır durumdaki satırın yaklaşık yerini tutar. Hata kutusu sayfada kalır, böylece kullanıcı mesajı kaçırmaz. Rozetin varyantı veriye göre seçilir; variant prop’u yalnızca kaynak kodda tanımlı dört değerden birini kabul ettiği için TypeScript yanlış bir değeri derleme sırasında yakalar.
Hangisini ne zaman kullanmalı?
Bu beş bileşen birbirinin yerine geçmez; her biri farklı bir soruya cevap verir. Kullanıcının okuyup davranması gereken bir mesaj varsa ve sayfada kalması gerekiyorsa Alert kullanın. Bir nesnenin durumunu (“Ödendi”, “Taslak”) ya da bir sayıyı kısa ve göze çarpan biçimde etiketlemek istiyorsanız Badge doğru seçimdir; data table hücrelerinde durum sütunu için sık kullanılır.
Bekleme durumlarında ayrım şöyledir: işin ne kadarının bittiğini biliyorsanız Progress; içeriğin şeklini biliyor ama verisini bekliyorsanız Skeleton; ikisini de bilmiyor, yalnızca “bir şey oluyor” demek istiyorsanız Spinner. Örneğin bir dosya yüklemesinde Progress, bir kart listesi yüklenirken Skeleton, bir buton tıklamasından sonra gelen kısa sunucu isteğinde Spinner uygun düşer.
Sık yapılan hatalar

- Alert içinde ikonu yanlış yere koymak. İki sütunlu düzen yalnızca SVG ikon
Alertbileşeninin doğrudan çocuğu olduğunda devreye girer; ikonuAlertTitleiçine koyarsanız düzen değişmez. - Uzun metni
AlertTitleiçine yazmak. Başlık tek satırla sınırlıdır ve taşan kısım kesilir; ayrıntıyıAlertDescriptioniçine yazın. - Progress’e yüzde olmayan bir değer vermek. Gösterge
100 - valueyüzde sola kaydırıldığı için değer 0-100 aralığında olmalıdır. - Skeleton’a boyut vermemek. Bileşenin kendi boyutu yoktur;
h-*vew-*sınıfları olmadan görünmez. - Spinner’lı butonu etkin bırakmak. Resmî örneklerde yükleniyor durumundaki butonlar
disabledyapılır. - Badge’i bağlantı yapmak için
aetiketini Badge içine koymak. Belgede önerilen yolasChildkullanmaktır; aksi hâlde birspaniçinde bağlantı oluşur ve bağlantıya özel üzerine gelme stilleri çalışmaz.
Sık Sorulan Sorular
Alert ile Sonner bildirimi arasındaki fark nedir?
Alert sayfanın içinde kalan bir uyarı kutusudur; Sonner bildirimi ise geçici olarak görünüp kaybolur. Kalıcı ve önemli mesajlar için Alert, işlem sonuçları için Sonner kullanılır.
Badge’e yeni bir renk varyantı ekleyebilir miyim?
Evet. Badge varyantları kaynak dosyada cva ile tanımlanır; components/ui/badge.tsx içindeki variants.variant nesnesine yeni bir anahtar ekleyebilirsiniz. Tek seferlik değişiklikler için resmî demodaki gibi className ile ezmek de yeterlidir.
Spinner neden Ekim 2025’te eklendi, önceden nasıl yapılıyordu?
Sürüm notları, Spinner ve Kbd’yi her gün tekrar tekrar yazılan sıkıcı parçalar için hazırlanmış yeniden kullanılabilir bileşenler olarak tanıtır ve bunların her bileşen kütüphanesiyle çalıştığını belirtir. Bileşen yalnızca dönen bir ikon olduğu için, önceki projelerde aynı şeyi bir lucide ikonuna animate-spin vererek kendiniz yapabiliyordunuz.
Progress belirsiz (indeterminate) modda çalışır mı?
shadcn belgesi yalnızca value ile kullanımı gösterir ve kaynak kod value yoksa çubuğu boş çizer. Süresi bilinmeyen beklemeler için belgede önerilen bileşen Spinner’dır.
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 · Sonner bildirimleri · Item, Empty, Kbd, Avatar · Button varyantları · cva ile özelleştirme · Tema değişkenleri