KTUIKTUI
KTUIKTUI
ComponentsDocsStudio

Getting Started

IntroductionInstallationApproachThemingJavaScriptTypeScriptDark modeRTLReferencesChangelog - v1.2.2Metronic TemplatePopular

Components

AccordionAvatarAlertBadgeBreadcrumbButtonCardCarouselNewClipboardNewCheckboxCollapseDatatableDismissDrawerDropdownImage InputInputKbdLinkModalPaginationPin inputNewProgressRadio GroupRange SliderNewRatingNewReparentRepeaterNewScrollableScrollspyScrolltoSelectSeparatorSkeletonStepperStickySwitchTabsTextareaTheme SwitchToastTooltipToggleToggle GroupToggle PasswordTooltip
©2026 KtUI. All rights reserved.
A project by Keenthemes
Docs
Carousel

Carousel

Horizontal slideshow with prev/next, optional pagination, thumbnails, autoplay, and scroll-snap, implemented as KTCarousel with data-kt-carousel* markup. Styling uses KtUI and Metronic tokens.

Examples

Basic

Root holds a viewport (scroll container) and slides (data-kt-carousel-item). Add tabIndex={0} (or tabindex="0" in HTML) so ArrowLeft / ArrowRight work when the carousel is focused.

<div
  class="mx-auto w-full max-w-lg space-y-3"
  data-kt-carousel="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="flex gap-4 overflow-x-auto scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      First slide
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Second slide
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Third slide
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Keyboard

Put tabindex="0" (or tabIndex={0}) on the carousel root so it can receive focus, then use ArrowLeft / ArrowRight to go to the previous or next slide while focus is inside the carousel (including on the root). The handler skips arrow keys when focus is in an input, textarea, or contenteditable node. In RTL, arrows follow visual next/previous.

<div class="mx-auto w-full max-w-lg space-y-3">
  <p class="text-muted-foreground text-center text-sm leading-relaxed">
    Focus the carousel (click inside it or press<!-- -->
    <span class="kt-kbd kt-kbd-xs kt-kbd-outline">Tab</span>), then use<!-- -->
    <span class="kt-kbd kt-kbd-xs kt-kbd-outline">←</span> /
    <span class="kt-kbd kt-kbd-xs kt-kbd-outline">→</span>
    <!-- -->to move between slides. Arrow keys are ignored while typing in
    inputs.
  </p>
  <div
    class="rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
    data-kt-carousel="true"
    tabindex="0"
    role="region"
    aria-roledescription="carousel"
    aria-label="Keyboard carousel example"
  >
    <div
      data-kt-carousel-viewport="true"
      class="flex gap-4 overflow-x-auto scroll-smooth"
    >
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
      >
        Slide 1
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
      >
        Slide 2
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
      >
        Slide 3
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
      >
        Slide 4
      </div>
    </div>
    <div class="flex justify-center gap-2 pt-3">
      <button
        type="button"
        class="kt-btn kt-btn-outline kt-btn-sm"
        data-kt-carousel-prev="true"
      >
        Previous</button
      ><button
        type="button"
        class="kt-btn kt-btn-outline kt-btn-sm"
        data-kt-carousel-next="true"
      >
        Next
      </button>
    </div>
  </div>
</div>

Scrollbar visible

By default the viewport and thumbnail strip use hidden native scrollbars (scrolling still works). Set data-kt-carousel-show-scrollbar="true" on the root—or pass showScrollbar: true to getOrCreateInstance—to show them. This example also adds kt-scrollable on the viewport for KtUI’s thin scrollbar styling.

<div
  class="mx-auto w-full max-w-lg space-y-3"
  data-kt-carousel="true"
  data-kt-carousel-show-scrollbar="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="kt-scrollable flex gap-4 overflow-x-auto scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      First slide
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Second slide
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Third slide
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Overlay arrows

Add prev/next buttons as overlay controls inside a relative carousel wrapper so they stay visible and easy to click.

<div
  class="mx-auto w-full max-w-lg"
  data-kt-carousel="true"
  data-kt-carousel-infinite-loop="true"
  tabindex="0"
>
  <div class="relative overflow-hidden rounded-xl border border-border">
    <div
      data-kt-carousel-viewport="true"
      class="bg-muted flex overflow-x-auto scroll-smooth"
    >
      <div
        data-kt-carousel-item="true"
        class="flex min-w-full shrink-0 items-center justify-center py-16 text-sm font-medium"
      >
        First slide
      </div>
      <div
        data-kt-carousel-item="true"
        class="flex min-w-full shrink-0 items-center justify-center py-16 text-sm font-medium"
      >
        Second slide
      </div>
      <div
        data-kt-carousel-item="true"
        class="flex min-w-full shrink-0 items-center justify-center py-16 text-sm font-medium"
      >
        Third slide
      </div>
    </div>
    <button
      type="button"
      class="absolute top-1/2 z-10 inline-flex size-10 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-xs transition-all duration-200 hover:scale-105 hover:border-primary/40 hover:bg-primary/10 hover:text-primary hover:shadow-sm focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
      style="left: 0.75rem; cursor: pointer"
      data-kt-carousel-prev="true"
      aria-label="Previous slide"
    >
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="lucide lucide-chevron-left size-5"
        aria-hidden="true"
      >
        <path d="m15 18-6-6 6-6"></path>
      </svg></button
    ><button
      type="button"
      class="absolute top-1/2 z-10 inline-flex size-10 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-xs transition-all duration-200 hover:scale-105 hover:border-primary/40 hover:bg-primary/10 hover:text-primary hover:shadow-sm focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
      style="right: 0.75rem; cursor: pointer"
      data-kt-carousel-next="true"
      aria-label="Next slide"
    >
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="lucide lucide-chevron-right size-5"
        aria-hidden="true"
      >
        <path d="m9 18 6-6-6-6"></path>
      </svg>
    </button>
  </div>
</div>

Pagination

Place data-kt-carousel-pagination on a wrapper; each dot is data-kt-carousel-pagination-item in slide order. Active item gets aria-current="true".

<div
  class="mx-auto w-full max-w-lg space-y-4"
  data-kt-carousel="true"
  tabindex="0"
>
  <div class="relative overflow-hidden rounded-xl border border-border">
    <div
      data-kt-carousel-viewport="true"
      class="flex gap-4 overflow-x-auto scroll-smooth"
    >
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center py-16 text-sm font-medium"
      >
        Slide 1
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center py-16 text-sm font-medium"
      >
        Slide 2
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center py-16 text-sm font-medium"
      >
        Slide 3
      </div>
    </div>
    <div
      data-kt-carousel-pagination="true"
      class="absolute bottom-0 left-0 right-0 z-10 flex justify-center gap-2 bg-transparent px-2 py-2"
      role="tablist"
      aria-label="Slides"
    >
      <button
        type="button"
        data-kt-carousel-pagination-item="true"
        class="size-2.5 shrink-0 rounded-full bg-background p-0 opacity-90 transition-opacity hover:opacity-100 aria-[current=true]:bg-primary aria-[current=true]:opacity-100"
        aria-label="Go to slide 1"
      ></button
      ><button
        type="button"
        data-kt-carousel-pagination-item="true"
        class="size-2.5 shrink-0 rounded-full bg-background p-0 opacity-90 transition-opacity hover:opacity-100 aria-[current=true]:bg-primary aria-[current=true]:opacity-100"
        aria-label="Go to slide 2"
      ></button
      ><button
        type="button"
        data-kt-carousel-pagination-item="true"
        class="size-2.5 shrink-0 rounded-full bg-background p-0 opacity-90 transition-opacity hover:opacity-100 aria-[current=true]:bg-primary aria-[current=true]:opacity-100"
        aria-label="Go to slide 3"
      ></button>
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Autoplay

Autoplay respects prefers-reduced-motion: reduce (timer is not started). Hover and focus-in pause autoplay when data-kt-carousel-pause-on-hover is true (default).

<div
  class="mx-auto w-full max-w-lg space-y-3"
  data-kt-carousel="true"
  data-kt-carousel-autoplay="true"
  data-kt-carousel-autoplay-interval="3500"
  data-kt-carousel-infinite-loop="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="flex gap-4 overflow-x-auto scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Auto 1
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Auto 2
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Auto 3
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Infinite loop

At the last slide, Next wraps to the first; at the first, Previous wraps to the last.

<div
  class="mx-auto w-full max-w-lg space-y-3"
  data-kt-carousel="true"
  data-kt-carousel-infinite-loop="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="flex gap-4 overflow-x-auto scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Loop A
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Loop B
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Loop C
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

RTL

Set dir="rtl" on the root (or rely on document direction). Arrow keys follow visual next / previous in RTL.

<div
  class="mx-auto w-full max-w-lg space-y-3"
  dir="rtl"
  data-kt-carousel="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="flex gap-4 overflow-x-auto scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      يمين
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      وسط
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      يسار
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      السابق</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      التالي
    </button>
  </div>
</div>

Multiple slides

Use CSS (min-w-[45%], etc.) so several slides peek into the viewport; the component still advances one slide index per next/prev.

<div
  class="mx-auto w-full max-w-xl space-y-3"
  data-kt-carousel="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="flex gap-3 overflow-x-auto scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      One
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Two
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Three
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Four
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Five
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Six
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Centered

data-kt-carousel-centered="true" scrolls the active slide with inline: "center".

<div
  class="mx-auto w-full max-w-xl space-y-3"
  data-kt-carousel="true"
  data-kt-carousel-centered="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="flex gap-3 overflow-x-auto scroll-smooth px-8"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[70%] min-w-[70%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[50%] sm:min-w-[50%]"
    >
      A
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[70%] min-w-[70%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[50%] sm:min-w-[50%]"
    >
      B
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[70%] min-w-[70%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[50%] sm:min-w-[50%]"
    >
      C
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[70%] min-w-[70%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[50%] sm:min-w-[50%]"
    >
      D
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[70%] min-w-[70%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[50%] sm:min-w-[50%]"
    >
      E
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[70%] min-w-[70%] shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[50%] sm:min-w-[50%]"
    >
      F
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Draggable

Pointer-drag on the viewport scrolls horizontally. Not combined with snap in this API: when data-kt-carousel-snap="true", draggable behavior is disabled.

<div
  class="mx-auto w-full max-w-xl space-y-3"
  data-kt-carousel="true"
  data-kt-carousel-draggable="true"
  tabindex="0"
>
  <p class="text-muted-foreground text-center text-xs">
    Drag horizontally on the track (snap is off).
  </p>
  <div
    data-kt-carousel-viewport="true"
    class="flex cursor-grab gap-3 overflow-x-auto scroll-smooth active:cursor-grabbing"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 select-none items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Drag 1
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 select-none items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Drag 2
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 select-none items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Drag 3
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 select-none items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Drag 4
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 select-none items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Drag 5
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 select-none items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Drag 6
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Auto height

data-kt-carousel-auto-height="true" sets the viewport height from the active slide (see ResizeObserver in implementation).

<div
  class="mx-auto w-full max-w-lg space-y-3"
  data-kt-carousel="true"
  data-kt-carousel-auto-height="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="flex gap-4 overflow-x-hidden overflow-y-visible scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-8 text-sm font-medium"
    >
      Short slide
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-20 text-sm font-medium"
    >
      Taller slide
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-12 text-sm font-medium"
    >
      Medium slide
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Snap

Add Tailwind snap-x / snap-mandatory on the viewport and snap-center on items; enable data-kt-carousel-snap="true" so drag is not attached.

<div
  class="mx-auto w-full max-w-xl space-y-3"
  data-kt-carousel="true"
  data-kt-carousel-snap="true"
  tabindex="0"
>
  <p class="text-muted-foreground text-center text-xs">
    Viewport uses scroll snap; draggable is disabled when snap is on.
  </p>
  <div
    data-kt-carousel-viewport="true"
    class="flex snap-x snap-mandatory gap-3 overflow-x-auto scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 snap-center items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Snap 1
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 snap-center items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Snap 2
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 snap-center items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Snap 3
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 snap-center items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Snap 4
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 snap-center items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Snap 5
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex w-[45%] min-w-[45%] shrink-0 snap-center items-center justify-center rounded-lg py-12 text-sm font-medium sm:w-[31%] sm:min-w-[31%]"
    >
      Snap 6
    </div>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Info

data-kt-carousel-current and data-kt-carousel-total show 1-based current index and slide count.

<div
  class="mx-auto w-full max-w-lg space-y-3"
  data-kt-carousel="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="flex gap-4 overflow-x-auto scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Alpha
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Beta
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-16 text-sm font-medium"
    >
      Gamma
    </div>
  </div>
  <div class="flex items-center justify-between gap-2">
    <div class="text-muted-foreground text-sm tabular-nums">
      <span data-kt-carousel-current="true" class="text-foreground font-medium"
        >0</span
      ><span> / </span><span data-kt-carousel-total="true">0</span>
    </div>
    <div class="flex gap-2">
      <button
        type="button"
        class="kt-btn kt-btn-outline kt-btn-sm"
        data-kt-carousel-prev="true"
      >
        Previous</button
      ><button
        type="button"
        class="kt-btn kt-btn-outline kt-btn-sm"
        data-kt-carousel-next="true"
      >
        Next
      </button>
    </div>
  </div>
</div>

Thumbnails (horizontal)

data-kt-carousel-thumbnails wraps controls; each data-kt-carousel-thumbnail maps to a slide by DOM order.

<div
  class="mx-auto w-full max-w-xl space-y-4"
  data-kt-carousel="true"
  tabindex="0"
>
  <div
    data-kt-carousel-viewport="true"
    class="flex gap-3 overflow-x-auto scroll-smooth"
  >
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
    >
      T1
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
    >
      T2
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
    >
      T3
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
    >
      T4
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
    >
      T5
    </div>
    <div
      data-kt-carousel-item="true"
      class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
    >
      T6
    </div>
  </div>
  <div
    data-kt-carousel-thumbnails="true"
    class="flex justify-center gap-2 overflow-x-auto scroll-smooth pb-1"
    role="tablist"
    aria-label="Thumbnails"
  >
    <button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground shrink-0 rounded-md border px-3 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 1"
    >
      T1</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground shrink-0 rounded-md border px-3 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 2"
    >
      T2</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground shrink-0 rounded-md border px-3 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 3"
    >
      T3</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground shrink-0 rounded-md border px-3 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 4"
    >
      T4</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground shrink-0 rounded-md border px-3 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 5"
    >
      T5</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground shrink-0 rounded-md border px-3 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 6"
    >
      T6
    </button>
  </div>
  <div class="flex justify-center gap-2">
    <button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-prev="true"
    >
      Previous</button
    ><button
      type="button"
      class="kt-btn kt-btn-outline kt-btn-sm"
      data-kt-carousel-next="true"
    >
      Next
    </button>
  </div>
</div>

Thumbnails (vertical)

Same behavior with a column layout.

<div
  class="mx-auto flex w-full max-w-2xl gap-4"
  data-kt-carousel="true"
  tabindex="0"
>
  <div
    data-kt-carousel-thumbnails="true"
    class="flex w-24 shrink-0 flex-col gap-2 overflow-y-auto scroll-smooth"
    role="tablist"
    aria-label="Thumbnails"
  >
    <button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground rounded-md border px-2 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 1"
    >
      V1</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground rounded-md border px-2 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 2"
    >
      V2</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground rounded-md border px-2 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 3"
    >
      V3</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground rounded-md border px-2 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 4"
    >
      V4</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground rounded-md border px-2 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 5"
    >
      V5</button
    ><button
      type="button"
      data-kt-carousel-thumbnail="true"
      class="bg-muted hover:bg-accent border-border text-muted-foreground hover:text-foreground rounded-md border px-2 py-2 text-xs font-medium data-[kt-carousel-thumbnail-active]:border-primary data-[kt-carousel-thumbnail-active]:text-foreground"
      aria-label="Thumbnail 6"
    >
      V6
    </button>
  </div>
  <div class="min-w-0 flex-1 space-y-3">
    <div
      data-kt-carousel-viewport="true"
      class="flex gap-3 overflow-x-auto scroll-smooth"
    >
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
      >
        V1
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
      >
        V2
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
      >
        V3
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
      >
        V4
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
      >
        V5
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
      >
        V6
      </div>
    </div>
    <div class="flex justify-center gap-2">
      <button
        type="button"
        class="kt-btn kt-btn-outline kt-btn-sm"
        data-kt-carousel-prev="true"
      >
        Previous</button
      ><button
        type="button"
        class="kt-btn kt-btn-outline kt-btn-sm"
        data-kt-carousel-next="true"
      >
        Next
      </button>
    </div>
  </div>
</div>

Destroy & reinitialize

Use KTCarousel.getInstance(root)?.dispose() then KTCarousel.getOrCreateInstance(root) after dynamic updates.

<div class="mx-auto w-full max-w-lg space-y-4">
  <div
    id="ktui-carousel-destroy-root"
    class="space-y-3"
    data-kt-carousel="true"
    tabindex="0"
  >
    <div
      data-kt-carousel-viewport="true"
      class="flex gap-4 overflow-x-auto scroll-smooth"
    >
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
      >
        A
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
      >
        B
      </div>
      <div
        data-kt-carousel-item="true"
        class="bg-muted flex min-w-full shrink-0 items-center justify-center rounded-lg py-14 text-sm font-medium"
      >
        C
      </div>
    </div>
    <div class="flex justify-center gap-2">
      <button
        type="button"
        class="kt-btn kt-btn-outline kt-btn-sm"
        data-kt-carousel-prev="true"
      >
        Previous</button
      ><button
        type="button"
        class="kt-btn kt-btn-outline kt-btn-sm"
        data-kt-carousel-next="true"
      >
        Next
      </button>
    </div>
  </div>
  <div class="flex flex-wrap justify-center gap-2">
    <button
      type="button"
      id="ktui-carousel-destroy-btn"
      class="kt-btn kt-btn-outline kt-btn-sm"
      disabled=""
    >
      Destroy carousel</button
    ><button
      type="button"
      id="ktui-carousel-reinit-btn"
      class="kt-btn kt-btn-primary kt-btn-sm"
      disabled=""
    >
      Reinitialize
    </button>
  </div>
  <script>
    (function () {
      function init() {
        var root = document.getElementById("ktui-carousel-destroy-root");
        var destroyBtn = document.getElementById("ktui-carousel-destroy-btn");
        var reinitBtn = document.getElementById("ktui-carousel-reinit-btn");
        if (!root || !destroyBtn || !reinitBtn || !window.KTCarousel) return;
        if (destroyBtn.getAttribute("data-kt-carousel-destroy-demo") === "1")
          return;
        destroyBtn.setAttribute("data-kt-carousel-destroy-demo", "1");

        function refreshButtons() {
          var inst = window.KTCarousel.getInstance(root);
          destroyBtn.toggleAttribute("disabled", !inst);
          reinitBtn.toggleAttribute("disabled", !!inst);
        }

        destroyBtn.addEventListener("click", function () {
          var inst = window.KTCarousel.getInstance(root);
          if (inst) inst.dispose();
          refreshButtons();
        });

        reinitBtn.addEventListener("click", function () {
          window.KTCarousel.getOrCreateInstance(root);
          refreshButtons();
        });

        /* After DOMContentLoaded: this demo's handler runs before KTUI's; defer so KTCarousel.init() has run. */
        setTimeout(refreshButtons, 0);
      }

      /* Inline script runs before ktui.min.js in the preview document; wait for DOM + bundle. */
      function schedule() {
        if (document.readyState === "loading") {
          document.addEventListener("DOMContentLoaded", init);
        } else if (window.KTCarousel) {
          init();
        } else {
          window.addEventListener("load", init);
        }
      }
      schedule();
    })();
  </script>
</div>

Usage

Markup

<div data-kt-carousel tabindex="0">
  <div data-kt-carousel-viewport class="flex gap-4 overflow-x-auto scroll-smooth">
    <div data-kt-carousel-item class="min-w-full shrink-0">…</div>
    <div data-kt-carousel-item class="min-w-full shrink-0">…</div>
  </div>
  <button type="button" data-kt-carousel-prev>Previous</button>
  <button type="button" data-kt-carousel-next>Next</button>
</div>

If you omit data-kt-carousel-viewport, the root is treated as the scroll container (only works when slides live inside the scrolling element).

Component API

Options

NameTypeDefaultDescription
data-kt-carouselflag—Enables auto-init on the root.
data-kt-carousel-viewportflag—

Snap vs draggable

Do not rely on drag and CSS scroll-snap together: with data-kt-carousel-snap="true", drag handlers are not attached. Use snap or drag—they are mutually exclusive in this API.

Methods

MethodDescription
goTo(index, userInitiated?)Scrolls to slide index.
next(userInitiated?) / prev(userInitiated?)Moves one slide (respects infinite).
getIndex()Active slide index (0-based).

Static methods

MethodDescription
KTCarousel.init()Initializes [data-kt-carousel] except lazy roots.
KTCarousel.createInstances()Same discovery after DOM updates.
KTCarousel.getInstance(element)Instance or null.

Events

EventWhenevent.detail.payload
kt.carousel.changeActive index changes{ index, prevIndex, userInitiated }
root.addEventListener('kt.carousel.change', (e) => {
  console.log(e.detail.payload.index);
});

Accessibility

  • Put tabindex="0" on the root (or ensure a child is focused) so ArrowLeft / ArrowRight navigate when focus is inside the carousel.
  • Arrow keys are ignored while focus is in an input, textarea, or contenteditable.
  • Pagination and thumbnails use aria-current="true" on the active control.
  • Autoplay is disabled when the user prefers reduced motion.

TypeScript

import {
  KTCarousel,
  type KTCarouselConfigInterface,
  type KTCarouselInterface,
  type KTCarouselChangePayloadInterface,
} from '@keenthemes/ktui';
 
const root = document.querySelector<HTMLElement>('[data-kt-carousel]');
if (!root) throw new Error('missing root');
 
const carousel: KTCarouselInterface | null =
  KTCarousel.getOrCreateInstance(root, {
    infiniteLoop: true,
    autoplay: false,
  } satisfies KTCarouselConfigInterface);
 







PreviouseCardNextClipboard

On This Page

  • Examples
    • Basic
    • Keyboard
    • Scrollbar visible
    • Overlay arrows
    • Pagination
    • Autoplay
    • Infinite loop
    • RTL
    • Multiple slides
    • Centered
    • Draggable
    • Auto height
    • Snap
    • Info
    • Thumbnails (horizontal)
    • Thumbnails (vertical)
    • Destroy & reinitialize
  • Usage
    • Markup
  • Component API
    • Options
    • Snap vs draggable
    • Methods
    • Static methods
    • Events
  • Accessibility
  • TypeScript
Scroll container; defaults to root when omitted.
data-kt-carousel-itemflag—Each slide (discovered under the viewport).
data-kt-carousel-prev / data-kt-carousel-nextflag—Previous / next controls.
data-kt-carousel-paginationflag—Wrapper for pagination items.
data-kt-carousel-pagination-itemflag—One control per slide, in order.
data-kt-carousel-thumbnailsflag—Thumbnail strip wrapper.
data-kt-carousel-thumbnailflag—One thumb per slide, in order.
data-kt-carousel-currentflag—Element whose text is set to current slide (1-based).
data-kt-carousel-totalflag—Element whose text is set to slide count.
data-kt-carousel-autoplaybooleanfalseAdvance on an interval.
data-kt-carousel-autoplay-intervalnumber4000Interval in ms (minimum 200).
data-kt-carousel-infinite-loopbooleanfalseWrap prev/next at ends.
data-kt-carousel-rtlbooleanfalseForce RTL logic (also respects dir / computed direction).
data-kt-carousel-centeredbooleanfalsescrollIntoView with inline: "center".
data-kt-carousel-draggablebooleanfalsePointer drag; ignored when snap is on.
data-kt-carousel-snapbooleanfalseDisables draggable; pair with CSS scroll-snap.
data-kt-carousel-auto-heightbooleanfalseViewport height tracks active slide.
data-kt-carousel-show-scrollbarbooleanfalseKeep native scrollbars on viewport and thumbnail strip.
data-kt-carousel-pause-on-hoverbooleantruePause autoplay on hover / focus-in.
data-kt-carousel-change-eventstringkt.carousel.changeCustom event name for slide changes.
data-kt-carousel-lazybooleanfalseSkip createInstances; use getOrCreateInstance.
getSlideCount()Number of slides.
getOption(name)Merged config / data option.
getElement()Root element.
dispose()Clears timers, listeners, KTData entry.
on / offInstance event map (see Events).
KTCarousel.getOrCreateInstance(element, config?)Creates if missing; returns null if there are no slides.
if
(carousel) {
const payload: KTCarouselChangePayloadInterface = {
index: carousel.getIndex(),
prevIndex: carousel.getIndex(),
userInitiated: false,
};
console.log(payload);
}