Skip to Content
⭐️ Leave a star →
ComponentsCarousel

Carousel

A headless, scroll-snap carousel (Embla-style). It tracks the active slide from scroll position and exposes Previous/Next buttons, indicators, keyboard arrows, and optional looping.

Features

Native scroll-snap — tracks the active slide from scroll position.
Horizontal or vertical orientation.
Previous/Next buttons auto-disable at the ends (unless looping).
Render-prop indicators reflect the selected slide.
Keyboard arrow navigation on the focused viewport.
Optional looping wraps from the last slide to the first.

Example

Slide 1
Slide 2
Slide 3
Slide 4
import { Carousel } from '@wire-ui/react' <Carousel.Root> <Carousel.Viewport> <Carousel.Content> <Carousel.Slide>Slide 1</Carousel.Slide> <Carousel.Slide>Slide 2</Carousel.Slide> <Carousel.Slide>Slide 3</Carousel.Slide> </Carousel.Content> </Carousel.Viewport> <Carousel.Previous>‹</Carousel.Previous> <Carousel.Next>›</Carousel.Next> <Carousel.Indicators> {({ index, selected, scrollTo }) => ( <button key={index} onClick={scrollTo} aria-current={selected} /> )} </Carousel.Indicators> </Carousel.Root>

Styling

Carousel is headless. The Root sets data-orientation and the viewport applies the native scroll-snap behavior inline. Slides snap to the start edge. Previous/Next become disabled automatically when they cannot scroll further (use disabled: variants to style that state).

<Carousel.Previous className=" border border-black disabled:opacity-30 disabled:cursor-not-allowed "> </Carousel.Previous>

Using data attributes

Carousel.Root sets data-orientation; the internal elements are tagged for targeting.

/* Stack a vertical carousel */ [data-orientation="vertical"] [data-carousel-content] { flex-direction: column; } /* Target the scroll container */ [data-carousel-viewport] { scrollbar-width: none; }

Root props

PropTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'Scroll axis
loopbooleanfalseWrap from last → first slide
defaultIndexnumber0Initial slide index
onIndexChange(index: number) => voidCalled when the selected slide changes

Root also accepts all native div attributes (except onChange).

Viewport props

PropTypeDescription
...React.HTMLAttributes<HTMLDivElement>The scroll container. Add tabIndex={0} to enable arrow-key navigation.

Content props

PropTypeDescription
...React.HTMLAttributes<HTMLDivElement>The flex track that holds the slides

Slide props

PropTypeDescription
...React.HTMLAttributes<HTMLDivElement>A single snap target; auto-registers with Root

Previous / Next props

PropTypeDescription
disabledbooleanOverride the automatic disabled state
...React.ButtonHTMLAttributes<HTMLButtonElement>Standard button attributes

Indicators props

PropTypeDescription
children(props: { index: number; selected: boolean; scrollTo: () => void }) => ReactNodeRender function called once per slide

Data attributes

AttributeElementValues
data-orientationRoot"horizontal" / "vertical"
data-carousel-viewportViewportPresent (marker)
data-carousel-contentContentPresent (marker)
data-carousel-slideSlidePresent (marker)

Accessibility

  • Root sets role="region" and aria-roledescription="carousel".
  • Each Slide sets role="group" and aria-roledescription="slide".
  • Previous/Next render as <button> with aria-label="Previous slide" / "Next slide" and are disabled at the ends (unless loop).

Keyboard Interactions

Arrow keys work when the focused element is the Viewport (give it tabIndex={0}).

KeyDescription
ArrowRight / ArrowDownScroll to the next slide (axis depends on orientation).
ArrowLeft / ArrowUpScroll to the previous slide.
TabMoves focus to the viewport / controls.
Last updated on

MIT License © 2026 wire-ui

Carousel – Wire UI