HextaUI

Carousel

Dokunmatik ekranda ivmeli, fare sürüklemeli, ok tuşlu, noktalı, küçük resimli yerel scroll-snap slaytları ve gerektiğinde duran bir otomatik oynatma.

import {
  Carousel,
  CarouselContent,
  CarouselDots,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"

const slides = [1, 2, 3, 4, 5]

export function CarouselDemo() {
  return (
    <div className="w-full max-w-xs px-12">
      <Carousel aria-label="Numbers">
        <CarouselContent>
          {slides.map((value) => (
            <CarouselItem key={value}>
              <div className="flex aspect-square items-center justify-center rounded-xl border bg-card p-6 text-4xl font-semibold">
                {value}
              </div>
            </CarouselItem>
          ))}
        </CarouselContent>
        <CarouselPrevious />
        <CarouselNext />
        <div className="mt-3 flex justify-center">
          <CarouselDots />
        </div>
      </Carousel>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/carousel.json

Bileşeni, HextaUI tema token'larını ve bileşenin bağımlı olduğu tüm HextaUI bileşenlerini ekler.

import {
  Carousel,
  CarouselContent,
  CarouselDots,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"
<Carousel aria-label="Featured">
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
  <CarouselDots />
</Carousel>

Slaytlar CSS scroll snap ile yerel olarak kayar; böylece dokunmatik ivme ve trackpad kaydırması platform gibi hissettirir. Fare sürükleyebilir ve ok tuşları her seferinde bir slayt ilerler.

Carousel
├── CarouselContent
│   └── CarouselItem
├── CarouselPrevious
├── CarouselNext
├── CarouselDots
├── CarouselCounter
├── CarouselAutoplayToggle
└── CarouselThumbnails
    └── CarouselThumbnail

API

Carousel API'sini almak için setApi geçin, sonra kendi konumunuzu göstermek için select'i dinleyin. Next son slaytta devre dışıdır ancak odağı korur.

"use client"

import * as React from "react"

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
  type CarouselApi,
} from "@/components/ui/carousel"

const slides = [1, 2, 3, 4, 5]

export function CarouselWithApi() {
  const [api, setApi] = React.useState<CarouselApi>()
  const [current, setCurrent] = React.useState(0)
  const [count, setCount] = React.useState(0)

  React.useEffect(() => {
    if (!api) {
      return
    }
    const sync = () => {
      setCount(api.scrollSnapList().length)
      setCurrent(api.selectedScrollSnap() + 1)
    }
    sync()
    api.on("select", sync).on("reInit", sync)
    return () => {
      api.off("select", sync).off("reInit", sync)
    }
  }, [api])

  return (
    <div className="flex w-full max-w-xs flex-col items-center gap-2 px-12">
      <Carousel setApi={setApi} aria-label="Numbers" className="w-full">
        <CarouselContent>
          {slides.map((value) => (
            <CarouselItem key={value}>
              <div className="flex aspect-square items-center justify-center rounded-xl border bg-card p-6 text-4xl font-semibold">
                {value}
              </div>
            </CarouselItem>
          ))}
        </CarouselContent>
        <CarouselPrevious />
        <CarouselNext />
      </Carousel>
      <p className="text-sm text-muted-foreground">
        Slide {current} of {count}
      </p>
    </div>
  )
}

Görünümde birkaç tane

Öğeler kendi basis değerini belirler. Boşluklar spacing prop'undan gelir; bu yüzden her basis'te tam kalır.

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"

export function CarouselSeveralPerView() {
  return (
    <div className="w-full max-w-md px-12">
      <Carousel spacing="sm" aria-label="Several per view">
        <CarouselContent>
          {Array.from({ length: 9 }, (_, index) => (
            <CarouselItem key={index} className="basis-1/2 sm:basis-1/3">
              <div className="flex aspect-square items-center justify-center rounded-xl border bg-card p-6 text-3xl font-semibold">
                {index + 1}
              </div>
            </CarouselItem>
          ))}
        </CarouselContent>
        <CarouselPrevious />
        <CarouselNext />
      </Carousel>
    </div>
  )
}

Noktalar ve sayaç

Etkin nokta, slaytlar kaydıkça uzar ve komşuları yer açar. Sayaç yalnızca değişen basamağı döndürür.

import {
  Carousel,
  CarouselContent,
  CarouselCounter,
  CarouselDots,
  CarouselItem,
} from "@/components/ui/carousel"

export function CarouselDotsAndCounter() {
  return (
    <Carousel aria-label="Dots and counter" className="w-full max-w-md">
      <CarouselContent>
        {Array.from({ length: 12 }, (_, index) => (
          <CarouselItem key={index} className="basis-4/5">
            <div className="flex aspect-square items-center justify-center rounded-xl border bg-card p-6 text-4xl font-semibold">
              {index + 1}
            </div>
          </CarouselItem>
        ))}
      </CarouselContent>
      <div className="mt-3 flex items-center justify-between gap-4">
        <CarouselCounter />
        <CarouselDots />
      </div>
    </Carousel>
  )
}

Otomatik oynatma

autoplay varsayılan olarak kapalıdır ve azaltılmış hareketle kapalı kalır. Üzerine gelince, klavye odağında, dokunmada, sürüklemede, gizli bir sekmede veya görünümden çıkıldığında duraklar; zamanlayıcı çalışırken etkin nokta dolar.

import {
  Carousel,
  CarouselAutoplayToggle,
  CarouselContent,
  CarouselDots,
  CarouselItem,
} from "@/components/ui/carousel"

const slides = [1, 2, 3, 4, 5]

export function CarouselAutoplay() {
  return (
    <Carousel
      autoplay={{ delay: 3000 }}
      aria-label="Autoplay"
      className="w-full max-w-xs"
    >
      <CarouselContent>
        {slides.map((value) => (
          <CarouselItem key={value}>
            <div className="flex aspect-square items-center justify-center rounded-xl border bg-card p-6 text-4xl font-semibold">
              {value}
            </div>
          </CarouselItem>
        ))}
      </CarouselContent>
      <div className="mt-3 flex items-center justify-center gap-2">
        <CarouselAutoplayToggle />
        <CarouselDots />
      </div>
    </Carousel>
  )
}

Küçük resimler

<CarouselThumbnails /> ana carousel'i izler ve etkin küçük resmi görünür tutmak için kayar.

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselThumbnail,
  CarouselThumbnails,
} from "@/components/ui/carousel"

const photos = Array.from({ length: 10 }, (_, index) => index + 1)

export function CarouselWithThumbnails() {
  return (
    <Carousel aria-label="Gallery" className="w-full max-w-md">
      <CarouselContent>
        {photos.map((photo) => (
          <CarouselItem key={photo}>
            <div className="relative overflow-hidden rounded-xl border">
              <img
                src="/preview/landscape.svg"
                alt={`Landscape ${photo}`}
                draggable={false}
                className="aspect-video w-full object-cover"
              />
              <span className="absolute start-3 top-3 rounded-md bg-background px-2 py-1 text-xs font-medium">
                {photo}
              </span>
            </div>
          </CarouselItem>
        ))}
      </CarouselContent>
      <div className="mt-3">
        <CarouselThumbnails>
          {photos.map((photo) => (
            <CarouselThumbnail key={photo} className="w-20">
              <img
                src="/preview/landscape.svg"
                alt=""
                draggable={false}
                className="aspect-video w-full object-cover"
              />
            </CarouselThumbnail>
          ))}
        </CarouselThumbnails>
      </div>
    </Carousel>
  )
}

Dikey

orientation="vertical" için <CarouselContent /> üzerinde bir yükseklik gerekir. Düğmeler üste ve alta taşınır.

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"

const slides = [1, 2, 3, 4, 5]

export function CarouselVertical() {
  return (
    <div className="w-full max-w-xs py-12">
      <Carousel orientation="vertical" aria-label="Vertical">
        <CarouselContent className="h-52">
          {slides.map((value) => (
            <CarouselItem key={value} className="basis-1/2">
              <div className="flex h-full items-center justify-center rounded-xl border bg-card p-6 text-3xl font-semibold">
                {value}
              </div>
            </CarouselItem>
          ))}
        </CarouselContent>
        <CarouselPrevious />
        <CarouselNext />
      </Carousel>
    </div>
  )
}

Kontrollü

index ve onIndexChange geçin. Kaydırma state'inizi günceller, state'iniz de carousel'i kaydırır.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Carousel,
  CarouselContent,
  CarouselDots,
  CarouselItem,
} from "@/components/ui/carousel"

const slides = [1, 2, 3, 4, 5]

export function CarouselControlled() {
  const [index, setIndex] = React.useState(2)

  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <div className="flex flex-wrap items-center gap-2">
        {slides.map((value, position) => (
          <Button
            key={value}
            size="sm"
            variant={position === index ? "secondary" : "outline"}
            onClick={() => setIndex(position)}
          >
            {value}
          </Button>
        ))}
        <span className="text-sm text-muted-foreground">index = {index}</span>
      </div>
      <Carousel index={index} onIndexChange={setIndex} aria-label="Controlled">
        <CarouselContent>
          {slides.map((value) => (
            <CarouselItem key={value}>
              <div className="flex aspect-square items-center justify-center rounded-xl border bg-card p-6 text-4xl font-semibold">
                {value}
              </div>
            </CarouselItem>
          ))}
        </CarouselContent>
        <div className="mt-3 flex justify-center">
          <CarouselDots />
        </div>
      </Carousel>
    </div>
  )
}

Başa sarma ve başlangıç dizini

rewind son slayttaki Next'i ilkine geri gönderir. defaultIndex bir slaytta kaydırma animasyonu olmadan açılır.

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"

const slides = [1, 2, 3, 4, 5]

export function CarouselRewind() {
  return (
    <div className="w-full max-w-xs px-12">
      <Carousel rewind defaultIndex={2} aria-label="Rewind">
        <CarouselContent>
          {slides.map((value) => (
            <CarouselItem key={value}>
              <div className="flex aspect-square items-center justify-center rounded-xl border bg-card p-6 text-4xl font-semibold">
                {value}
              </div>
            </CarouselItem>
          ))}
        </CarouselContent>
        <CarouselPrevious />
        <CarouselNext />
      </Carousel>
    </div>
  )
}

Bir bağlantıyı fareyle sürüklemek, onu açmadan kaydırır. Ekran dışındaki bir slayta Tab ile gelmek onu görünüme kaydırır.

import {
  Carousel,
  CarouselContent,
  CarouselDots,
  CarouselItem,
} from "@/components/ui/carousel"

const articles = Array.from({ length: 6 }, (_, index) => index + 1)

export function CarouselLinks() {
  return (
    <Carousel spacing="sm" aria-label="Articles" className="w-full max-w-md">
      <CarouselContent>
        {articles.map((article) => (
          <CarouselItem key={article} className="basis-4/5 sm:basis-1/2">
            <a
              href="#"
              className="flex h-32 flex-col justify-end gap-1 rounded-xl border bg-card p-4 outline-none focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden"
            >
              <span className="text-sm font-medium">Article {article}</span>
              <span className="text-sm text-muted-foreground">
                Read the full story
              </span>
            </a>
          </CarouselItem>
        ))}
      </CarouselContent>
      <div className="mt-3 flex justify-center">
        <CarouselDots />
      </div>
    </Carousel>
  )
}

Slayt ekleme ve kaldırma

Noktalar, sayaç ve düğmeler slaytlar gelip gittikçe güncellenir.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Carousel,
  CarouselContent,
  CarouselCounter,
  CarouselDots,
  CarouselItem,
} from "@/components/ui/carousel"

export function CarouselDynamic() {
  const [slides, setSlides] = React.useState([1, 2, 3])

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <div className="flex gap-2">
        <Button
          size="sm"
          variant="outline"
          onClick={() => setSlides([...slides, slides.length + 1])}
        >
          Add slide
        </Button>
        <Button
          size="sm"
          variant="outline"
          disabled={slides.length === 0}
          onClick={() => setSlides(slides.slice(0, -1))}
        >
          Remove slide
        </Button>
      </div>
      <Carousel aria-label="Dynamic">
        <CarouselContent>
          {slides.map((value) => (
            <CarouselItem key={value} className="basis-1/2">
              <div className="flex aspect-square items-center justify-center rounded-xl border bg-card p-6 text-4xl font-semibold">
                {value}
              </div>
            </CarouselItem>
          ))}
        </CarouselContent>
        <div className="mt-3 flex items-center justify-between">
          <CarouselCounter />
          <CarouselDots />
        </div>
      </Carousel>
    </div>
  )
}

İç içe

Ok tuşları, sürükleme ve noktalar yalnızca içinde bulunduğunuz carousel'i hareket ettirir.

import {
  Carousel,
  CarouselContent,
  CarouselDots,
  CarouselItem,
} from "@/components/ui/carousel"

const outerSlides = [1, 2, 3]
const innerSlides = [1, 2, 3, 4, 5]

export function CarouselNested() {
  return (
    <Carousel aria-label="Outer" className="w-full max-w-md">
      <CarouselContent>
        {outerSlides.map((outer) => (
          <CarouselItem key={outer}>
            <div className="flex flex-col gap-3 rounded-xl border bg-card p-4">
              <p className="text-sm font-medium">Outer slide {outer}</p>
              <Carousel spacing="sm" aria-label={`Inner ${outer}`}>
                <CarouselContent>
                  {innerSlides.map((inner) => (
                    <CarouselItem key={inner} className="basis-1/3">
                      <div className="flex aspect-square items-center justify-center rounded-lg bg-muted text-lg font-medium">
                        {outer}.{inner}
                      </div>
                    </CarouselItem>
                  ))}
                </CarouselContent>
                <div className="mt-2 flex justify-center">
                  <CarouselDots />
                </div>
              </Carousel>
            </div>
          </CarouselItem>
        ))}
      </CarouselContent>
      <div className="mt-3 flex justify-center">
        <CarouselDots />
      </div>
    </Carousel>
  )
}

Uzun içerik ve tek slayt

Kırılmayan metin slaytının içinde sarar. Tek slaytla noktalar gizlenir ve düğmeler devre dışı kalır.

import {
  Carousel,
  CarouselContent,
  CarouselCounter,
  CarouselDots,
  CarouselItem,
} from "@/components/ui/carousel"

export function CarouselLongContent() {
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <Carousel aria-label="Long content">
        <CarouselContent>
          <CarouselItem className="basis-4/5">
            <div className="rounded-xl border bg-card p-4 text-sm wrap-anywhere">
              Supercalifragilisticexpialidocious_with_an_unbroken_string_that_never_ends_and_keeps_going_well_past_the_edge
            </div>
          </CarouselItem>
          <CarouselItem className="basis-4/5">
            <div className="rounded-xl border bg-card p-4 text-sm">
              مرحبا 你好 👩‍👩‍👧‍👦 A second slide with mixed scripts.
            </div>
          </CarouselItem>
        </CarouselContent>
        <div className="mt-3 flex justify-center">
          <CarouselDots />
        </div>
      </Carousel>
      <Carousel aria-label="Single slide">
        <CarouselContent>
          <CarouselItem>
            <div className="flex aspect-video items-center justify-center rounded-xl border bg-card p-6 text-2xl font-semibold">
              Only one
            </div>
          </CarouselItem>
        </CarouselContent>
        <div className="mt-3 flex items-center justify-between">
          <CarouselCounter />
          <CarouselDots />
        </div>
      </Carousel>
    </div>
  )
}

Sağdan sola

Slaytlar sağdan başlar, oklar ters döner, Sol ok tuşu ileri gider ve noktalar sağdan dolar.

import {
  Carousel,
  CarouselContent,
  CarouselCounter,
  CarouselDots,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"

const slides = [1, 2, 3, 4, 5]

export function CarouselRtl() {
  return (
    <div dir="rtl" className="w-full max-w-md px-12">
      <Carousel autoplay={{ delay: 4000 }} aria-label="شرائح">
        <CarouselContent>
          {slides.map((value) => (
            <CarouselItem key={value} className="basis-1/2">
              <div className="flex aspect-square items-center justify-center rounded-xl border bg-card p-6 text-4xl font-semibold">
                {value}
              </div>
            </CarouselItem>
          ))}
        </CarouselContent>
        <CarouselPrevious />
        <CarouselNext />
        <div className="mt-3 flex items-center justify-between">
          <CarouselCounter />
          <CarouselDots />
        </div>
      </Carousel>
    </div>
  )
}

Odak carousel'in herhangi bir yerindeyken tuşlar çalışır; metin alanları ve iç içe carousel'ler hariç.

TuşAction
→Sonraki slayt. Sağdan sola düzenlerde önceki. Dikey carousel'lerde ↓.
←Önceki slayt. Sağdan sola düzenlerde sonraki. Dikey carousel'lerde ↑.
TabDüğmeler, etkin nokta ve slaytların içeriği arasında gezinir; ekran dışındaki slaytları görünüme kaydırır.
EnterSpaceOdaktaki düğmeyi, noktayı veya küçük resmi etkinleştirir.
  • Kök, carousel olarak tanımlanan bir region'dır. Ona bir aria-label verin.
  • Her öğe, slayt olarak tanımlanan ve “3 of 5” gibi konumuyla etiketlenen bir group'tur.
  • Kibar (polite) bir canlı bölge, klavye veya düğmeyle gezindikten sonra yeni slayt'ı duyurur ve otomatik oynatma sürerken sessiz kalır.
  • Noktalar ve küçük resimler tek bir tab durağı kullanır ve odak etkin olanı izler.
  • Previous ve Next devre dışıyken de odaklanabilir kalır; böylece odak iki uçta da kaybolmaz.
PropTürVarsayılan
orientation
"horizontal" | "vertical""horizontal"
spacingSlaytlar arasındaki boşluk.
"none" | "sm" | "default" | "lg""default"
index
number–
defaultIndex
number0
onIndexChange
(index: number) => void–
rewindSon slayttan ilkine sarın.
booleanfalse
mouseDragSlaytları fareyle sürüklemeye izin verin.
booleantrue
autoplayBir zamanlayıcıyla ilerler. delay varsayılanı 5000ms, asgari 1000ms.
boolean | { delay?: number }false
setApi
(api: CarouselApi) => void–
render
ReactElement | (props, state) => ReactElement<div>
ÖznitelikAçıklama
data-slot="carousel"CSS'te kökü hedefleyin.
data-orientationYönelim.
--carousel-spacingspacing ile ayarlanan, slaytlar arasındaki boşluk.
PropTürVarsayılan
classNameSlaytları tutan şeride uygulanır.
string–
viewportClassNameKayan görünüm alanına uygulanır.
string–
ÖznitelikAçıklama
data-slot="carousel-content"Kayan görünüm alanı.
data-slot="carousel-container"İçindeki şerit.
data-scrollableBirden fazla konum olduğunda bulunur.
data-draggingFareyle sürükleme sürerken bulunur.
PropTürVarsayılan
render
ReactElement | (props, state) => ReactElement<div>
ÖznitelikAçıklama
data-slot="carousel-item"Görünümde birkaç tane göstermek için basis-* ayarlayın.

İkisi de bir <Button /> oluşturur ve onun prop'larını kabul eder. İçeriğin dışında dururlar; bu yüzden carousel'in çevresinde yer bırakın.

PropTürVarsayılan
variant
ButtonProps["variant"]"outline"
size
ButtonProps["size"]"icon-sm"
childrenYönelimi ve yönü izler.
ReactNodeArrow icon
ÖznitelikAçıklama
data-slot="carousel-previous"“Previous slide” olarak etiketlenir.
data-slot="carousel-next"“Next slide” olarak etiketlenir.
data-disabledİki uçtan birinde bulunur. Düğme odaklanabilir kalır.
PropTürVarsayılan
aria-label
string"Choose slide"
ÖznitelikAçıklama
data-slot="carousel-dots"Noktaların grubu. Tek konumda gizlenir.
data-slot="carousel-dot"Her nokta. Etkin olanda aria-current bulunur.
--dot-active0 ile 1 arası; kaydırma sırasında bir noktanın ne kadar etkin olduğu.
ÖznitelikAçıklama
data-slot="carousel-counter"Geçerli konumu toplamın üzerinde, dönen bir basamakla gösterir.
PropTürVarsayılan
variant
ButtonProps["variant"]"ghost"
size
ButtonProps["size"]"icon-sm"
ÖznitelikAçıklama
data-slot="carousel-autoplay-toggle"“Pause slideshow” veya “Play slideshow” olarak etiketlenir.
PropTürVarsayılan
aria-label
string"Slides"
ÖznitelikAçıklama
data-slot="carousel-thumbnails"Kayan şerit.
PropTürVarsayılan
indexAçtığı slayt. Varsayılan olarak şeritteki konumudur.
number–
render
ReactElement | (props, state) => ReactElement<button>
ÖznitelikAçıklama
data-slot="carousel-thumbnail"CSS'te küçük resimleri hedefleyin.
data-activeSlaytı görünümdeyken bulunur.

setApi ve useCarousel() ile döndürülür. Animasyonsuz hareket için jump: true geçin.

PropTürVarsayılan
scrollPrev
(jump?: boolean) => void–
scrollNext
(jump?: boolean) => void–
scrollToBir snap konumuna kaydırır.
(index: number, jump?: boolean) => void–
scrollToSlideBir slaytı gösteren konuma kaydırır.
(slideIndex: number, jump?: boolean) => void–
canScrollPrev
() => boolean–
canScrollNext
() => boolean–
selectedScrollSnap
() => number–
scrollSnapList
() => number[]–
slidesInView
() => number[]–
slideNodes
() => HTMLElement[]–
viewportNode
() => HTMLElement | null–
play
() => void–
stop
() => void–
isPlaying
() => boolean–
on / off
(event: "select" | "scroll" | "settle" | "reInit", listener) => CarouselApi–

Kendi kontrollerinizi oluşturmak için <Carousel /> içinde kullanın. api, orientation, selectedIndex, snapCount, slideCount, slidesInView, canScrollPrev, canScrollNext, isPlaying ve kaydırma ile oynatma yöntemlerini döndürür.