Sayfa düzeni için shadcn/ui beş küçük bileşen sunar: içeriği başlık, gövde ve alt bilgiyle çerçeveleyen Card, yatay ya da dikey çizgi çeken Separator, özel görünümlü kaydırma çubuğu sağlayan ScrollArea, sürüklenerek boyutlanan paneller için Resizable ve içeriği sabit en boy oranında tutan AspectRatio. Her biri npx shadcn@latest add card gibi ayrı bir komutla eklenir. Card tamamen düz HTML ve sınıflardan oluşur; Separator, Scroll Area ve Aspect Ratio Radix primitiflerine, Resizable ise react-resizable-panels kütüphanesine dayanır.

Bu bileşenlerin belge sayfaları kısa olduğundan, aşağıda belgedeki kullanım örneklerini depodaki resmî demolar ve kaynak kodla birlikte okuyup her birinin davranışını ve sık yapılan hataları ayrı ayrı anlatıyoruz. Genel sayfa tasarımı ilkeleri için sayfa tasarımı rehberine, serinin tamamı için shadcn/ui öğrenme rehberi sayfasına bakabilirsiniz.

Card: başlık, içerik, eylem ve alt bilgi

Card: başlık, içerik, eylem ve alt bilgi

Card, belgede başlık, içerik ve alt bilgi içeren bir kart olarak tanımlanır. Bir Radix paketine ihtiyaç duymaz; elle kurulumda yalnızca components/ui/card.tsx dosyasını kopyalarsınız. Yedi parça dışa aktarır: Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent ve CardFooter.

bash
npx shadcn@latest add card
typescript
<Card className="w-full max-w-sm">
  <CardHeader>
    <CardTitle>Hesabınıza giriş yapın</CardTitle>
    <CardDescription>
      Giriş yapmak için e-posta adresinizi girin
    </CardDescription>
    <CardAction>
      <Button variant="link">Kaydol</Button>
    </CardAction>
  </CardHeader>
  <CardContent>
    <form>
      <div className="grid gap-2">
        <Label htmlFor="email">E-posta</Label>
        <Input id="email" type="email" placeholder="ornek@alanadi.com" required />
      </div>
    </form>
  </CardContent>
  <CardFooter className="flex-col gap-2">
    <Button type="submit" className="w-full">
      Giriş yap
    </Button>
  </CardFooter>
</Card>

Bu örnek belgedeki card-demo dosyasının kısaltılmış ve Türkçeleştirilmiş hâlidir. Kaynak kodda dikkat çeken birkaç tasarım kararı var. Card kartın arka plan ve ön plan rengini --card ve --card-foreground tema değişkenlerinden alır, yuvarlatılmış köşe, kenarlık ve hafif gölge ekler; parçalar arasındaki dikey boşluğu da kendisi verir. CardHeader bir ızgaradır: içinde CardAction varsa ızgara iki sütuna geçer ve eylem sağ üst köşeye, başlık ile açıklamanın yanına yerleşir. Başlık alanına border-b sınıfı verirseniz alt boşluk otomatik eklenir; aynı kural CardFooter için border-t ile geçerlidir. CardFooter kaynakta öğeleri yatay sıralayan ve dikeyde ortalayan esnek bir kutudur; demo iki düğmeyi alt alta dizmek için ona flex-col gap-2 verir ve düğmelere w-full ekler.

CardTitle ve CardDescription kaynakta birer div öğesidir, h2 ya da p değildir. Kart başlığının belge yapısında gerçek bir başlık olması gerekiyorsa içine uygun başlık etiketini koyabilir ya da kod sizin olduğu için card.tsx dosyasında etiketi değiştirebilirsiniz. Kartın renkleri için tema ve CSS değişkenleri yazısına bakın.

Separator: içeriği görsel ya da anlamsal olarak ayırmak

Separator, belgede içeriği görsel ya da anlamsal olarak ayıran bileşen olarak geçer ve @radix-ui/react-separator üzerine kuruludur. Kaynakta iki varsayılan vardır: orientation="horizontal" ve decorative={true}. Yatay ayırıcı bir piksel yüksekliğinde ve tam genişlikte, dikey ayırıcı bir piksel genişliğinde ve tam yükseklikte çizilir; renk --border değişkeninden gelir.

typescript
<div>
  <div className="space-y-1">
    <h4 className="text-sm leading-none font-medium">Radix Primitives</h4>
    <p className="text-muted-foreground text-sm">Açık kaynaklı bir arayüz bileşen kütüphanesi.</p>
  </div>
  <Separator className="my-4" />
  <div className="flex h-5 items-center space-x-4 text-sm">
    <div>Blog</div>
    <Separator orientation="vertical" />
    <div>Belgeler</div>
    <Separator orientation="vertical" />
    <div>Kaynak</div>
  </div>
</div>

Demodaki ayrıntıya dikkat edin: dikey ayırıcıların bulunduğu satıra h-5 yüksekliği verilmiştir. Dikey ayırıcı yüksekliğini kapsayıcısından aldığı için, yüksekliği belli olmayan bir kapsayıcıda görünmeyebilir. Resmî sidebar-07 bloğu aynı sorunu ayırıcının kendisine data-[orientation=vertical]:h-4 sınıfı vererek çözer. decorative değeri true olduğu sürece ayırıcı yalnızca görsel kabul edilir; içeriği anlamsal olarak bölen bir ayırıcı istiyorsanız decorative={false} verin ve ayrıntılar için Radix UI Separator sayfasına bakın.

Scroll Area: özel kaydırma çubuğu

Scroll Area: özel kaydırma çubuğu

Scroll Area, belgeye göre yerel kaydırmayı tarayıcılar arasında tutarlı ve özelleştirilebilir bir görünümle zenginleştirir. @radix-ui/react-scroll-area paketine dayanır ve iki parça dışa aktarır: ScrollArea ile ScrollBar. Kaynakta ScrollArea içeriğinizi bir görüntü alanına (viewport) koyar, sonuna dikey bir ScrollBar ve köşe öğesini kendiliğinden ekler. Görüntü alanı klavye odağı aldığında odak halkası gösterir.

typescript
import * as React from "react"

import { ScrollArea } from "@/components/ui/scroll-area"
import { Separator } from "@/components/ui/separator"

const etiketler = Array.from({ length: 50 }).map(
  (_, i, a) => `v1.2.0-beta.${a.length - i}`
)

export function EtiketListesi() {
  return (
    <ScrollArea className="h-72 w-48 rounded-md border">
      <div className="p-4">
        <h4 className="mb-4 text-sm leading-none font-medium">Etiketler</h4>
        {etiketler.map((etiket) => (
          <React.Fragment key={etiket}>
            <div className="text-sm">{etiket}</div>
            <Separator className="my-2" />
          </React.Fragment>
        ))}
      </div>
    </ScrollArea>
  )
}

Bu demodaki en önemli sınıf h-72 değeridir. Kaydırma ancak kapsayıcının yüksekliği içerikten küçük olduğunda oluşur; sabit ya da sınırlı bir yükseklik vermezseniz alan içeriğe göre uzar ve kaydırma çubuğu hiç görünmez. Belgedeki kullanım örneği de aynı nedenle h-[200px] gibi açık bir yükseklik verir.

Yatay kaydırma

Yatay kaydırma için kaynak kendiliğinden yalnızca dikey çubuk eklediğinden, ScrollArea içine ayrıca ScrollBar orientation="horizontal" koyarsınız. Belgedeki yatay demo kapsayıcıya whitespace-nowrap, içerideki satıra flex w-max verir; böylece öğeler alt satıra kaymaz ve satır kapsayıcıdan geniş kalır:

typescript
<ScrollArea className="w-96 rounded-md border whitespace-nowrap">
  <div className="flex w-max space-x-4 p-4">
    {gorseller.map((g) => (
      <figure key={g.sanatci} className="shrink-0">
        {/* görsel ve açıklama */}
      </figure>
    ))}
  </div>
  <ScrollBar orientation="horizontal" />
</ScrollArea>

Kaydırma çubuğunun görünümü kaynakta ScrollBar bileşeninde tanımlıdır: dikey çubuk tam yükseklikte ve ince bir genişlikte, yatay çubuk tam genişlikte ve aynı incelikte çizilir; içindeki tutamak --border renginde ve tamamen yuvarlatılmıştır. Çubuk dokunma hareketlerini ve metin seçimini kendine almaz. Çubuğu kalınlaştırmak ya da rengini değiştirmek istiyorsanız ScrollBar bileşenine className verebilir ya da tutamak rengini değiştirmek için scroll-area.tsx dosyasını düzenleyebilirsiniz, çünkü tutamağın sınıfı dışarıdan verilen bir özelliğe bağlı değildir.

Resizable: boyutu değiştirilebilen paneller

Resizable, belgede klavye desteği olan erişilebilir, yeniden boyutlandırılabilir panel grupları olarak tanımlanır ve react-resizable-panels üzerine kuruludur. Sürüm geçmişindeki Aralık 2023 kaydına göre fare, dokunma ve klavye ile çalışır. Elle kurulumda react-resizable-panels paketini yüklersiniz. Üç parça vardır: ResizablePanelGroup, ResizablePanel ve paneller arasına konan ResizableHandle.

bash
npx shadcn@latest add resizable
typescript
import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable"

export function PanelDuzeni() {
  return (
    <ResizablePanelGroup
      direction="horizontal"
      className="max-w-md rounded-lg border md:min-w-[450px]"
    >
      <ResizablePanel defaultSize={50}>
        <div className="flex h-[200px] items-center justify-center p-6">
          <span className="font-semibold">Bir</span>
        </div>
      </ResizablePanel>
      <ResizableHandle withHandle />
      <ResizablePanel defaultSize={50}>
        <ResizablePanelGroup direction="vertical">
          <ResizablePanel defaultSize={25}>
            <div className="flex h-full items-center justify-center p-6">
              <span className="font-semibold">İki</span>
            </div>
          </ResizablePanel>
          <ResizableHandle />
          <ResizablePanel defaultSize={75}>
            <div className="flex h-full items-center justify-center p-6">
              <span className="font-semibold">Üç</span>
            </div>
          </ResizablePanel>
        </ResizablePanelGroup>
      </ResizablePanel>
    </ResizablePanelGroup>
  )
}

Bu örnek belgedeki resizable-demo dosyasıyla aynı yapıdadır; tek fark ilk tutamağa withHandle eklenmesidir. direction özelliği yönü belirler: horizontal panelleri yan yana, vertical alt alta dizer. Grupları iç içe koyarak, örnekteki gibi sağ paneli kendi içinde dikey olarak bölebilirsiniz. defaultSize başlangıç boyutudur; demo bir gruptaki panellere toplamı 100 eden değerler verir (50 ve 50, 25 ve 75). withHandle tutamağın ortasına küçük bir tutma simgesi ekler; vermezseniz yalnızca ince bir çizgi görünür.

Kaynakta yön bilgisi data-panel-group-direction özniteliğinden okunur: dikey grupta kap dikey esnek kutuya döner, tutamak yatay bir çizgiye dönüşür ve tutma simgesi 90 derece döndürülür. Tutamağa klavye odağı geldiğinde bir odak halkası gösterilir. Bu stil kuralları ResizablePanelGroup ve ResizableHandle bileşenlerinde tanımlıdır; kütüphanenin diğer özellikleri için belge, kütüphanenin API sayfasına bağlantı verir.

Aspect Ratio: sabit en boy oranı

Aspect Ratio, içeriği istenen oranda gösterir ve @radix-ui/react-aspect-ratio paketine dayanır. Kaynak kodu yalnızca Radix kökünü data-slot ile sarar; tek önemli özellik ratio değeridir. Belgedeki demo, Next.js Image bileşenini fill ile kullanır ve görselin oranı korunarak kabı doldurması için object-cover sınıfı verir:

typescript
import Image from "next/image"

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function KapakGorseli() {
  return (
    <AspectRatio ratio={16 / 9} className="bg-muted rounded-lg">
      <Image
        src="/kapak.jpg"
        alt="Kapak görseli"
        fill
        className="h-full w-full rounded-lg object-cover"
      />
    </AspectRatio>
  )
}

ratio bir sayı alır; 16 / 9 yazmak, değeri elle hesaplamaktan daha okunaklıdır. Kaba verilen bg-muted sınıfı, görsel yüklenene kadar alanın boş görünmemesini sağlar. Görseldeki fill özelliği Next.js'e özgüdür; başka bir ortamda düz img etiketi kullanıyorsanız ona tam genişlik ve yükseklik verip object-cover uygularsınız. Demo, koyu temada görseli karartmak için ayrıca dark: önekli sınıflar kullanır; bu önekin kendisi Tailwind konusudur. Aspect Ratio, slaytlı galerilerde de işe yarar: Carousel içindeki her CarouselItem içine bir AspectRatio koyduğunuzda, farklı boyutlardaki görseller aynı oranda kırpılır ve slaytların yüksekliği eşit kalır.

Mini örnek: liste ve ayrıntı paneli

Mini örnek: liste ve ayrıntı paneli: Card varyantı neden yok?, data-slot ile dışarıdan stil vermek

Bu beş bileşenin birlikte nasıl çalıştığını görmek için e-posta istemcilerine benzeyen bir düzen kuralım: solda kaydırılabilir bir liste, sağda seçili öğenin ayrıntısını gösteren bir kart, ikisinin arasında boyutu değiştirilebilen bir tutamak. Örnek yalnızca belgede ve demolarda geçen özellikleri kullanır; içerik yer tutucudur.

typescript
import { AspectRatio } from "@/components/ui/aspect-ratio"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Separator } from "@/components/ui/separator"

const bildirimler = Array.from({ length: 30 }).map((_, i) => `Bildirim ${i + 1}`)

export function GelenKutusu() {
  return (
    <ResizablePanelGroup direction="horizontal" className="min-h-[400px] rounded-lg border">
      <ResizablePanel defaultSize={35}>
        <ScrollArea className="h-[400px]">
          <div className="p-4">
            {bildirimler.map((b) => (
              <div key={b}>
                <div className="text-sm">{b}</div>
                <Separator className="my-2" />
              </div>
            ))}
          </div>
        </ScrollArea>
      </ResizablePanel>
      <ResizableHandle withHandle />
      <ResizablePanel defaultSize={65}>
        <div className="p-6">
          <Card>
            <CardHeader className="border-b">
              <CardTitle>Bildirim 1</CardTitle>
              <CardDescription>Seçili bildirimin ayrıntısı</CardDescription>
            </CardHeader>
            <CardContent>
              <AspectRatio ratio={16 / 9} className="bg-muted rounded-lg" />
            </CardContent>
          </Card>
        </div>
      </ResizablePanel>
    </ResizablePanelGroup>
  )
}

Bu düzende her bileşen tek bir işi üstlenir. ResizablePanelGroup iki paneli yan yana koyar ve başlangıçta alanı 35 ile 65 olarak böler; kullanıcı tutamağı sürükleyerek bu oranı değiştirebilir. Sol paneldeki ScrollArea sabit h-[400px] yüksekliği sayesinde otuz satırı kendi içinde kaydırır; yükseklik verilmeseydi liste paneli uzatırdı. Satır aralarındaki Separator yatay ve dekoratiftir. Sağdaki Card başlığına border-b sınıfı verdiğimiz için kaynaktaki kural başlığın altına otomatik boşluk ekler. En altta AspectRatio, içine henüz görsel konmamış olsa bile 16:9 oranında soluk bir alan ayırır; görsel geldiğinde yerleşim kaymaz. Seçili öğeyi gerçekten değiştirmek için duruma ve tıklama işleyicisine ihtiyacınız olur, bu da bileşeni istemci bileşeni yapar.

Card varyantı neden yok?

Button gibi bileşenlerin aksine card.tsx kaynağında cva ile tanımlanmış bir varyant yoktur; her parça yalnızca cn() ile birleştirilen sabit sınıflar kullanır. Farklı görünümler için ya className verir ya da dosyayı açıp kendi varyantınızı eklersiniz. İkinci yolun nasıl yapıldığı cva ile bileşen özelleştirme yazısında anlatılıyor. Separator da benzer biçimde başka bileşenlerin içinde yeniden kullanılır: Sidebar kaynağındaki SidebarSeparator, Separator'ı kenar çubuğu kenarlık rengi ve yatay kenar boşluğuyla sarar.

data-slot ile dışarıdan stil vermek

Bu bileşenlerin hepsi kaynakta bir data-slot özniteliği taşır: card, card-header, card-title, card-action, card-content, card-footer, separator, scroll-area, scroll-area-viewport, scroll-area-scrollbar, resizable-panel-group, resizable-panel, resizable-handle ve aspect-ratio gibi. Card'ın kendisi bu öznitelikten yararlanır: CardHeader, içinde data-slot="card-action" taşıyan bir öğe olup olmadığına bakarak iki sütunlu ızgaraya geçer. Siz de üst bir kapsayıcıdan belirli bir parçayı bu özniteliklerle hedefleyebilir, örneğin bir sayfadaki tüm kart başlıklarının görünümünü tek bir yerden değiştirebilirsiniz. Bu yaklaşımın ayrıntısı ve cn() ile birlikte kullanımı cn fonksiyonu ve data-slot yazısında.

Hangisini ne zaman kullanmalı?

BileşenDayandığı yapıNe zamanKritik ayrıntı
CardDüz HTMLForm, özet kutusu, ürün kartıCardAction varsa başlık iki sütuna geçer
Separator@radix-ui/react-separatorBölümler arası çizgiDikey ayırıcı için kapsayıcı yüksekliği
Scroll Area@radix-ui/react-scroll-areaSınırlı alanda uzun listeSabit yükseklik, yatayda ScrollBar ekleme
Resizablereact-resizable-panelsDüzenleyici, panel düzenidirection ve defaultSize
Aspect Ratio@radix-ui/react-aspect-ratioGörsel, video, harita alanıratio değeri ve object-cover

Sık yapılan hatalar

  • ScrollArea'ya yükseklik vermemek: Alan içeriğe göre uzar ve kaydırma oluşmaz. h-72 ya da h-[200px] gibi bir sınır verin.
  • Yatay kaydırmada ScrollBar eklememek: Kaynak yalnızca dikey çubuğu kendiliğinden ekler; yatay için ScrollBar orientation="horizontal" gerekir.
  • Dikey Separator'ı görünmez bırakmak: Kapsayıcının yüksekliği yoksa dikey çizgi sıfır yükseklikte kalır.
  • CardTitle'ı başlık etiketi sanmak: Kaynakta bir div öğesidir; belge yapısında başlık gerekiyorsa bunu kendiniz sağlayın.
  • Resizable grubunu yüksekliksiz bırakmak: Kaynakta grup tam genişlik ve tam yükseklik alır; yatay demoda yükseklik panel içeriğine verilen h-[200px] ile gelir. Dikey grupta panellerin bölüşeceği bir yükseklik bulunmalıdır.
  • Aspect Ratio içindeki görseli boyutlandırmamak: Görsel kabı doldurmazsa oran kabı korunur ama içerik boşlukta kalır; demodaki gibi fill ya da tam boyut ve object-cover kullanın.

Sık Sorulan Sorular

Card için ayrı bir paket kurmam gerekir mi?

Hayır. Belgedeki elle kurulum adımı yalnızca card.tsx dosyasını kopyalamayı ve içe aktarma yollarını düzeltmeyi ister; bileşen düz HTML öğeleri ve cn() yardımcısıyla yazılmıştır. cn() için cn fonksiyonu yazısına bakın.

Scroll Area yerine düz overflow kullanabilir miyim?

Elbette; Scroll Area yerel kaydırmanın yerini almaz, onun üzerine tutarlı görünümlü bir kaydırma çubuğu ekler. Görünümün tarayıcıdan tarayıcıya aynı olması sizin için önemli değilse düz CSS yeterlidir.

Resizable panel boyutlarını kalıcı yapabilir miyim?

shadcn belgesi bu konuda örnek vermez. Bileşen react-resizable-panels özelliklerini olduğu gibi aktardığı için kalıcılık gibi seçenekler için kütüphanenin kendi belgesine bakmanız gerekir.

Aspect Ratio ile CSS aspect-ratio arasındaki fark nedir?

shadcn belgesi bir karşılaştırma yapmaz. Bileşen Radix'in Aspect Ratio primitifini sarar ve ratio özelliğiyle çalışır; resmî bloklarda ise Tailwind'in aspect-video sınıfının doğrudan kullanıldığı yerler de vardır. Hangisini seçeceğiniz projenizin stil yaklaşımına bağlı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 · Tabs, Accordion ve Collapsible · Carousel (Embla) · Sidebar kurulumu · cn fonksiyonu · Sayfa tasarımı rehberi