Skip to Content
Puzzle
UI ComponentsNavigation

Carousel

Browse a collection of items with carousel navigation.

Carousel displays a draggable collection with optional navigation and pagination. CarouselController owns the items and selection; Carousel controls their presentation.

Import

import { Carousel, CarouselController } from "@puzzle-ui/ui";

Basic usage

Supply items to the controller and renderItem to the carousel. Navigation buttons are opt-in: pass backButton and nextButton to support click and keyboard navigation as well as dragging.

390 × 340

Provide getItemKey when items can be inserted, removed, or reordered. Stable keys keep component state attached to its item; without it, keys use the item index.

Responsive panels and gap

panelsPerView controls how many items fit at once. Responsive values use mobile, tablet, and desktop, inheriting smaller breakpoints when omitted. Change the preview device to compare layouts.

Changing gap recalculates panel offsets and pagination, including when panel widths remain fixed. The active panel stays aligned to its updated position.

768 × 360

Item width and single-page alignment

Use itemWidth instead of panelsPerView for explicit item widths; it takes precedence when both are supplied. singlePageAlignment positions items when the whole collection fits on one page.

768 × 300

buttonAlignment="over" overlays navigation on the viewport; "aside" reserves space beside it. pagerAlignment supports "under" and "over". Both accept responsive values. renderPager receives a zero-based page index and the page count.

390 × 320

Controlled selection

currentPanel is the zero-based first visible item index, not a page number. Pair it with onPanelChange to keep application state synchronized. defaultPanel supplies an initial index for uncontrolled usage.

390 × 400

API

ResponsiveValue<T> accepts a value or an object with mobile, tablet, and desktop values.

CarouselController

PropTypeDefaultDescription
itemsT[]RequiredCollection shared with descendant carousels.
childrenReactNodeRequiredCarousel and optional custom controls.
currentPanelnumber-Controlled first visible item index.
defaultPanelnumber0Initial uncontrolled first visible item index.
onPanelChange(panel: number) => void-Called with the next first visible item index.
PropTypeDefaultDescription
renderItem(item: T, index: number) => ReactNodeRequiredRenders each item.
getItemKey(item: T, index: number) => KeyItem indexStable React key for each item.
panelsPerViewResponsiveValue<number>1Number of visible items in panel sizing mode.
itemWidthResponsiveValue<CSSProperties["width"]>-Explicit width; takes precedence over panel count.
gapResponsiveValue<CSSProperties["gap"]>0Space between items. Numbers are pixels.
singlePageAlignment"left" | "center" | "right""left"Alignment when all items fit on one page.
backButtonReactElement-Previous-page control; receives disabled state and click handling.
nextButtonReactElement-Next-page control; receives disabled state and click handling.
buttonAlignmentResponsiveValue<"aside" | "over">"over"Navigation placement.
pagerAlignmentResponsiveValue<"under" | "over">"under"Pager placement.
renderPager(currentPage: number, numberOfPages: number) => ReactNode-Custom pager; page index is zero-based.
ariaLabelstring"Carousel"Accessible name of the carousel group.
classNamestring-Additional class on the root.
viewportClassNamestring-Additional class on the clipping viewport.
viewportStyleCSSProperties-Styles on the viewport.

For custom controls within the controller, useCarouselContext<T>() exposes items, currentPanel, and setPanel(index).

Accessibility

Give each carousel a descriptive ariaLabel. Navigation elements must be real buttons with accessible names and forward their received props. They are disabled at the collection boundaries. Provide buttons rather than relying on pointer dragging alone.

;