shadcn/ui Carousel, kaydırılabilir slaytlar için hazır bir bileşendir ve Embla Carousel kütüphanesi üzerine kuruludur. npx shadcn@latest add carousel ile eklenir; Carousel içine bir CarouselContent, onun içine her slayt için bir CarouselItem ve isteğe bağlı olarak CarouselPrevious ile CarouselNext düğmelerini koyarsınız. Slayt genişliği basis-*, aralık pl-* ve -ml-* sınıflarıyla, yön orientation özelliğiyle, Embla seçenekleri opts ile, eklentiler plugins ile verilir; programlı kontrol için setApi ile Embla API nesnesini alırsınız.
Sürüm geçmişine göre Carousel Aralık 2023'te hareket, kaydırma hareketi ve klavye desteğiyle eklendi; aynı kayıt sonsuz döngü, otomatik oynatma ve dikey yönü desteklediğini belirtiyor. Bu yazıda belgedeki tüm örnekleri, kaynak koddaki davranışlarla birlikte görüyorsunuz. Embla'nın kendi API'si bu yazının konusu değil; belge her başlıkta Embla belgesine yönlendirir ve biz de aynı bağlantıları veriyoruz. Seri haritası shadcn/ui öğrenme rehberi sayfasında.
Kurulum ve temel kullanım

CLI ile kurulum tek komuttur. Depodaki kayıt tanımına göre Carousel, npm tarafında embla-carousel-react paketine, shadcn tarafında button bileşenine bağımlıdır; önceki ve sonraki düğmeleri Button ile çizilir. Elle kurulumda embla-carousel-react paketini yükleyip components/ui/carousel.tsx dosyasını kopyalarsınız.
npx shadcn@latest add carouselBelgedeki demo, beş slaytlık bir döngüyü Card bileşenleriyle kurar. Aşağıdaki örnek aynı yapıdadır; içe aktarmalar @/components/ui yollarına göre yazıldı:
import * as React from "react"
import { Card, CardContent } from "@/components/ui/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel"
export function UrunGalerisi() {
return (
<Carousel className="w-full max-w-xs">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}Bu kodda her slayt kaydırma alanının tam genişliğini kaplar, çünkü kaynakta CarouselItem varsayılan olarak basis-full sınıfı alır. Önceki ve sonraki düğmeleri yatay modda kaydırma alanının dışına, sol ve sağa yerleşir; kaynakta konumları -left-12 ve -right-12 sınıflarıyla verilir. Bu yüzden demo, döngüye max-w-xs gibi bir genişlik verir ve çevresinde boşluk bırakır. Düğmeler kaydırılacak slayt kalmadığında devre dışı olur; bu durum kaynakta Embla'nın canScrollPrev() ve canScrollNext() sonuçlarından hesaplanır.
Slayt boyutu: basis sınıfları
Bir ekranda birden çok slayt göstermek için CarouselItem bileşenine basis sınıfı verirsiniz. Belgedeki ilk örnekte her öğe döngü genişliğinin üçte birini kaplar; ikinci örnekte ise orta ekranda yarısını, büyük ekranda üçte birini:
// Döngü genişliğinin %33'ü.
<Carousel>
<CarouselContent>
<CarouselItem className="basis-1/3">...</CarouselItem>
<CarouselItem className="basis-1/3">...</CarouselItem>
<CarouselItem className="basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>
// Orta ekranda %50, büyük ekranda %33.
<Carousel>
<CarouselContent>
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>Sınıflar cn() ile birleştirildiği için verdiğiniz basis-1/3 değeri kaynaktaki basis-full değerini ezer; bu birleştirme mantığı cn fonksiyonu yazısında anlatılıyor. Duyarlı öneklerin (md, lg) kendisi Tailwind konusudur.
Slaytlar arası boşluk: pl ve -ml
Aralık için belge alışılmadık bir yöntem önerir: CarouselItem üzerinde sol iç boşluk (pl-*), CarouselContent üzerinde aynı büyüklükte negatif sol kenar boşluğu (-ml-*). Belgedeki notta shadcn, gap ya da ızgara düzeniyle doğru aralığı tutturmanın çok fazla hesap gerektirdiğini, bu iki sınıfın çok daha kolay olduğunu anlatır ve isterseniz kendi projenizde bunu değiştirebileceğinizi ekler.
<Carousel>
<CarouselContent className="-ml-2 md:-ml-4">
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
</CarouselContent>
</Carousel>Kaynak kodda varsayılan değerler zaten bu kalıptadır: yatay modda içerik kabı -ml-4, her öğe pl-4 alır; dikey modda ise sırasıyla -mt-4 ve pt-4. Bu yüzden aralığı değiştirirken iki tarafı birlikte güncellemeniz gerekir. Yalnızca öğedeki pl değerini değiştirirseniz ilk slayt kaymış görünür, çünkü kaptaki negatif boşluk eski değerde kalır.
Yön: orientation
orientation özelliği horizontal (varsayılan) ya da vertical değerini alır. Kaynakta bu değer Embla'ya axis seçeneği olarak çevrilir: yatayda x, dikeyde y. Dikey modda içerik kabı alt alta dizilir, düğmeler döngünün üstüne ve altına yerleşir ve 90 derece döndürülür. Belgedeki dikey demo, içerik kabına h-[200px] yüksekliği ve öğelere md:basis-1/2 verir; dikey kaydırmanın çalışması için kabın bir yüksekliği olmalıdır.
<Carousel
opts={{
align: "start",
}}
orientation="vertical"
className="w-full max-w-xs"
>
<CarouselContent className="-mt-1 h-[200px]">
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="pt-1 md:basis-1/2">
<div className="p-1">
<Card>
<CardContent className="flex items-center justify-center p-6">
<span className="text-3xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>Bu demoda aralık -mt-1 ve pt-1 ile daraltılmıştır; dikey modda aralık için pl değil pt kullanıldığını unutmayın.
Carousel özellikleri

Kaynak koddaki CarouselProps tipi, kök bileşenin div özelliklerine ek olarak dört özellik tanımlar. Tablo bu tipe göre hazırlandı; değer aralıkları Embla'ya ait olanlar için Embla belgesine yönlendiriyoruz:
| Özellik | Tip (kaynaktaki tanım) | Varsayılan | Açıklama |
|---|---|---|---|
| orientation | horizontal veya vertical | horizontal | Kaydırma yönü; Embla axis değerine çevrilir |
| opts | useEmblaCarousel ilk parametresi | yok | Embla seçenekleri (align, loop ve diğerleri) |
| plugins | useEmblaCarousel ikinci parametresi | yok | Embla eklentileri dizisi |
| setApi | (api: CarouselApi) => void | yok | Embla API nesnesini dışarı verir |
| className ve diğerleri | div öğesinin React özellikleri | yok | Kök div öğesine aktarılır |
Tablodaki son satır önemlidir: onMouseEnter, aria-label ya da id gibi tüm ek özellikler kök div öğesine geçer. Autoplay demosundaki onMouseEnter ve onMouseLeave kullanımı da bu sayede çalışır. Düğmeler ise Button özelliklerini alır; kaynakta varsayılanları variant="outline" ve size="icon" olduğu için başka bir varyant isterseniz doğrudan özellik olarak verebilirsiniz.
Seçenekler: opts
opts özelliği doğrudan Embla seçeneklerini alır; belge örnek olarak align: "start" ve loop: true gösterir ve tüm seçenekler için Embla seçenekleri sayfasına yönlendirir. Kaynakta opts nesnesi Embla'ya aktarılırken axis değeri her zaman orientation özelliğinden hesaplanıp üzerine yazılır; yani yönü opts içinden değil, orientation ile belirlemelisiniz.
<Carousel
opts={{
align: "start",
loop: true,
}}
>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>API: setApi ile slayt sayacı
Döngüyü koddan kontrol etmek ya da durumunu okumak için belge setApi özelliğini ve bir React durumunu kullanır. CarouselApi tipi aynı dosyadan dışa aktarılır. Aşağıdaki örnek belgedeki sayaç örneğini tamamlar: toplam slayt sayısı scrollSnapList().length, geçerli slayt selectedScrollSnap() + 1 ile okunur ve select olayında güncellenir.
"use client"
import * as React from "react"
import {
Carousel,
CarouselContent,
CarouselItem,
type CarouselApi,
} from "@/components/ui/carousel"
export function SayacliGaleri() {
const [api, setApi] = React.useState<CarouselApi>()
const [current, setCurrent] = React.useState(0)
const [count, setCount] = React.useState(0)
React.useEffect(() => {
if (!api) {
return
}
setCount(api.scrollSnapList().length)
setCurrent(api.selectedScrollSnap() + 1)
api.on("select", () => {
setCurrent(api.selectedScrollSnap() + 1)
})
}, [api])
return (
<div className="mx-auto max-w-xs">
<Carousel setApi={setApi} className="w-full max-w-xs">
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
<div className="text-muted-foreground py-2 text-center text-sm">
Slayt {current} / {count}
</div>
</div>
)
}Neden useEffect içinde if (!api) denetimi var? Kaynakta setApi, Embla API nesnesi oluştuktan sonra bir efekt içinde çağrılır. İlk render sırasında api değeri henüz undefined olduğundan, olay dinleyicisini bağlamadan önce bu denetim gerekir. api gelince efekt yeniden çalışır ve dinleyici kurulur. Ayrıca belgenin toplamı slayt öğelerini sayarak değil, scrollSnapList() ile kaydırma durak noktalarının listesinden okuduğuna dikkat edin. Bir ekranda birden çok slayt gösterdiğinizde bu liste ile öğe sayısının ilişkisi Embla'nın hizalama seçeneklerine bağlıdır; sayacın ne göstermesi gerektiğine karar verirken Embla belgesine bakın.
Olaylar: api.on
setApi ile aldığınız nesne üzerinden Embla olaylarını dinlersiniz. Belgedeki olay örneği select olayını kullanır; diğer olaylar için Embla olayları sayfasına bakmanızı ister.
React.useEffect(() => {
if (!api) {
return
}
api.on("select", () => {
// Slayt seçildiğinde yapılacak iş.
})
}, [api])Kaynak kodun kendisi de aynı API'yi kullanır: döngü içinde reInit ve select olaylarına bir işlev bağlanır ve bu işlev önceki ve sonraki düğmelerinin etkin olup olmadığını günceller. Kendi bileşeninizde bağladığınız dinleyiciler için temizleme işini, yani gerektiğinde api.off çağrısını siz yönetirsiniz; belge örneği temizleme göstermez.
Eklentiler: otomatik oynatma
plugins özelliği Embla eklentilerini alır. Belge örneği embla-carousel-autoplay paketindeki Autoplay eklentisini delay: 2000 ile kullanır; eklentinin kendisi ayrı bir pakettir. Depodaki demo bir adım daha ileri gider: eklentiyi React.useRef ile tek bir kez oluşturur, stopOnInteraction: true verir ve fare döngünün üzerine gelince stop, ayrılınca reset çağırır:
"use client"
import * as React from "react"
import Autoplay from "embla-carousel-autoplay"
import { Card, CardContent } from "@/components/ui/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel"
export function OtomatikGaleri() {
const plugin = React.useRef(
Autoplay({ delay: 2000, stopOnInteraction: true })
)
return (
<Carousel
plugins={[plugin.current]}
className="w-full max-w-xs"
onMouseEnter={plugin.current.stop}
onMouseLeave={plugin.current.reset}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}useRef kullanımının amacı, her render'da yeni bir eklenti örneği oluşturmamaktır; onMouseEnter ve onMouseLeave de aynı örneğin yöntemlerine bağlanır. Eklenti seçeneklerinin tamamı için belge Embla eklentileri sayfasını gösterir.
Mini örnek: kendi düğmelerinizle gezinme
Hazır CarouselPrevious ve CarouselNext düğmeleri döngünün dışına, iki yana yerleşir. Düğmeleri örneğin döngünün altına, Türkçe metinlerle koymak istiyorsanız iki yolunuz var. İlki, hazır düğmelere className vermektir; cn() ve tailwind-merge sayesinde verdiğiniz konum sınıfları kaynaktaki -left-12 gibi değerlerin yerine geçer. İkincisi, setApi ile aldığınız nesneyi kullanıp kendi düğmelerinizi yazmaktır. Kaynak kod da tam olarak bu yöntemleri çağırır: scrollPrev(), scrollNext(), canScrollPrev() ve canScrollNext().
"use client"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
Carousel,
CarouselContent,
CarouselItem,
type CarouselApi,
} from "@/components/ui/carousel"
export function AltDugmeliGaleri() {
const [api, setApi] = React.useState<CarouselApi>()
const [geri, setGeri] = React.useState(false)
const [ileri, setIleri] = React.useState(false)
React.useEffect(() => {
if (!api) {
return
}
const guncelle = () => {
setGeri(api.canScrollPrev())
setIleri(api.canScrollNext())
}
guncelle()
api.on("select", guncelle)
api.on("reInit", guncelle)
return () => {
api.off("select", guncelle)
api.off("reInit", guncelle)
}
}, [api])
return (
<div className="w-full max-w-xs">
<Carousel setApi={setApi}>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
<div className="flex justify-between pt-2">
<Button variant="outline" disabled={!geri} onClick={() => api?.scrollPrev()}>
Önceki
</Button>
<Button variant="outline" disabled={!ileri} onClick={() => api?.scrollNext()}>
Sonraki
</Button>
</div>
</div>
)
}Bu bileşen, kaynak koddaki iç mantığın dışarıya taşınmış bir kopyasıdır. Düğmelerin etkin olup olmadığını iki durum değişkeninde tutuyoruz ve hem select hem reInit olaylarında güncelliyoruz; kaynak kod da aynı iki olayı dinler. Farklı olarak efektten bir temizleme işlevi döndürüyor ve dinleyicileri api.off ile kaldırıyoruz. Düğmeler döngünün dışında olduğu için useCarousel bağlamına ihtiyaç duymazlar; tüm bilgi setApi ile alınan nesneden gelir. loop: true seçeneğiyle birlikte kullanırsanız düğmelerin hangi durumda devre dışı kalacağı Embla'nın döngü davranışına bağlıdır; bunun ayrıntısı Embla belgesindedir.
Kaynak kodda erişilebilirlik ve klavye
Kaynak koda göre Carousel kökü role="region" ve aria-roledescription="carousel" taşır; her CarouselItem ise role="group" ve aria-roledescription="slide" alır. Önceki ve sonraki düğmelerinde ekran okuyucular için gizli Previous slide ve Next slide metinleri vardır. Klavye desteği kökteki bir dinleyiciyle sağlanır: odak döngünün içindeyken sol ok tuşu önceki, sağ ok tuşu sonraki slayta geçirir. Bu dinleyici yalnızca ArrowLeft ve ArrowRight tuşlarına bakar; dikey modda da aynı tuşlar kullanılır. Dinleyici yakalama aşamasında (onKeyDownCapture) bağlanır ve bu iki tuşta tarayıcının varsayılan davranışını engeller. Bunun bir sonucu var: bir slaytın içine metin kutusu koyarsanız, sol ve sağ ok tuşları imleci hareket ettirmek yerine slayt değiştirir. Böyle bir içerik gerekiyorsa carousel.tsx içindeki işleyiciyi kendi ihtiyacınıza göre düzenleyin. Bölgeye anlamlı bir ad vermek isterseniz köke aria-label ekleyebilirsiniz, çünkü kalan özellikler div öğesine aktarılır. Kavramlar için ARIA yazısına bakın.
| Parça | Kaynaktaki öznitelik veya davranış |
|---|---|
| Carousel | role="region", aria-roledescription="carousel", ok tuşu dinleyicisi |
| CarouselContent | Embla referansı, overflow-hidden kap, -ml-4 veya -mt-4 |
| CarouselItem | role="group", aria-roledescription="slide", basis-full, pl-4 veya pt-4 |
| CarouselPrevious | outline varyantlı yuvarlak Button, canScrollPrev false ise devre dışı |
| CarouselNext | outline varyantlı yuvarlak Button, canScrollNext false ise devre dışı |
Sık yapılan hatalar

- Parçaları Carousel dışında kullanmak:
CarouselContent,CarouselItemve düğmeler bağlamıuseCarouselkancasından okur. Kök dışında kullanıldıklarında kaynakuseCarousel must be used within a <Carousel />hatasını fırlatır. - Düğmelerin taşmasına şaşırmak: Yatay modda düğmeler döngünün dışına yerleşir; dar bir kapsayıcıda kesilmemeleri için çevrede boşluk bırakın ya da
classNameile konumlarını değiştirin. - basis sınıfını yanlış parçaya vermek: Slayt genişliği
CarouselItemüzerinde belirlenir; belgebasis-*sınıfını her öğeye ayrı ayrı verir,CarouselContentbileşenine değil. - Aralığı tek taraftan değiştirmek:
pl-*değerini değiştirdiğinizdeCarouselContentüzerindeki-ml-*değerini de aynı büyüklüğe getirin. - Dikey modda yükseklik vermemek: Demodaki gibi
CarouselContentiçin bir yükseklik tanımlayın. - Yönü opts.axis ile vermeye çalışmak: Kaynak
axisdeğeriniorientationözelliğinden hesaplayıp üzerine yazar. - Autoplay eklentisini her render'da yeniden oluşturmak: Demodaki gibi
React.useRefile bir kez oluşturun. - İstemci bileşeni işaretini unutmak:
useStateveuseEffectkullanan sayaç ya da eklenti örnekleri Next.js App Router'da"use client"gerektirir. Ayrıntı için Next.js App Router yazısına bakın.
Sık Sorulan Sorular
Sonsuz döngü nasıl açılır?
opts={{ loop: true }} verin. Bu bir Embla seçeneğidir; belge örneği onu align: "start" ile birlikte gösterir.
Bir ekranda üç slayt göstermek için ne yapmalıyım?
Her CarouselItem bileşenine basis-1/3 verin. Ekran boyutuna göre değiştirmek için belgedeki gibi md:basis-1/2 lg:basis-1/3 kullanabilirsiniz.
Geçerli slaytın numarasını nasıl gösteririm?
setApi ile API nesnesini alın, scrollSnapList().length ile toplamı, selectedScrollSnap() ile geçerli dizini okuyun ve select olayında güncelleyin. Dizin sıfırdan başladığı için ekranda göstermek için bir ekleyin.
Carousel sunucu bileşeninde kullanılabilir mi?
carousel.tsx dosyası "use client" ile başlar, yani bileşenin kendisi istemci bileşenidir. Belgedeki temel demo hiçbir kanca kullanmadığı için dosyasında "use client" yoktur ve bir sunucu bileşeninin içinde render edilebilir. setApi, durum ya da useRef kullanan örnekler ise kendi dosyalarında "use client" ile başlar. Slaytlarda görsel gösterecekseniz oranı korumak için Aspect Ratio bileşenini slayt içine koyabilirsiniz.
Otomatik oynatma için ek paket gerekir mi?
Evet. Belge örneği Autoplay eklentisini embla-carousel-autoplay paketinden içe aktarır; bu paket carousel kaydının bağımlılıkları arasında değildir, ayrıca kurmanız gerekir. Paket kurulumunun genel adımları için Node.js ve npm kurulumu yazısına bakabilirsiniz.
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 · Card, Separator ve Scroll Area · cn fonksiyonu · Button varyantları · shadcn blocks · React ile modern web uygulaması