Basics
Carousel
Cycles through a series of slides inside a container. The indicators are small neutral dots and the active one uses the solid color of the palette.
Slides scroll and snap with native CSS scroll snapping, so touch swiping works out of the box. Every part is optional: compose only the arrows, indicators, autoplay button or progress text you need.
Import
import { Carousel } from "@indoku/ui"Anatomy
Carousel.Root (slideCount)├─ Carousel.ItemGroup│ └─ Carousel.Item (index)└─ Carousel.Control ├─ Carousel.PrevTrigger / Carousel.NextTrigger ├─ Carousel.Indicators shortcut for the group below │ = Carousel.IndicatorGroup > Carousel.Indicator (index) ├─ Carousel.AutoplayTrigger > Carousel.AutoplayIndicator └─ Carousel.ProgressTextExamples
Basic
slideCount is required: the carousel needs it to compute snap points during server rendering. Give every Carousel.Item its index.
Floating arrows
Put the triggers inside Carousel.Control next to the item group. Here they sit on top of the slide with absolute positioning.
Controlled
Use page and onPageChange to read and set the active slide from your own state.
Slide 1 of 5
Store
useCarousel returns a store you pass to Carousel.RootProvider. Call scrollTo, scrollNext and friends from anywhere, even outside the carousel.
Thumbnail indicators
Render Carousel.IndicatorGroup yourself and pass children to each Carousel.Indicator. Add unstyled to drop the dot styling, and style the active one with _current.
Multiple slides per page
slidesPerPage shows several slides at once and spacing sets the gap between them. Next and previous move one page at a time unless you set slidesPerMove.
Variable size
Add autoSize so each slide keeps its own width, and set width="auto" on the item. snapAlign controls where a slide stops.
const sizes = [120, 200, 80, 250, 150]function CarouselVariableSize() { return ( <Carousel.Root slideCount={sizes.length} autoSize spacing="8px" maxW="xl" mx="auto"> <Carousel.ItemGroup> {sizes.map((width, index) => ( <Carousel.Item key={index} index={index} snapAlign="center" width="auto"> <Center style={{ width }} h="24" rounded="l2" bg="bg.muted" borderWidth="1px"> {width}px </Center> </Carousel.Item> ))} </Carousel.ItemGroup> </Carousel.Root> )}Vertical
Set orientation="vertical" and give the root a height. The arrows and indicators stack vertically too.
Loop and mouse drag
loop wraps from the last slide to the first. allowMouseDrag lets desktop users drag, touch devices always can.
Drag with the mouse, or keep pressing next: it wraps around.
Autoplay
Pass autoplay (4 seconds by default) or { delay } in milliseconds. Carousel.AutoplayTrigger pauses and resumes it, and Carousel.ProgressText shows 1 / 5.
Lightbox
Compose the carousel with a Dialog for a gallery that opens on click.
Props
Root
| Prop | Type | Default | Description |
|---|---|---|---|
slideCount* | number | – | Total number of slides. Needed for SSR so snap points exist on the first render. |
page | number | – | The active page, for controlled usage. |
defaultPage | number | 0 | The initial page when uncontrolled. |
onPageChange | (details: { page: number }) => void | – | Called when the page changes. |
slidesPerPage | number | 1 | How many slides are visible at once. Decimals such as 1.5 show a peek of the next slide. |
slidesPerMove | number | 'auto' | 'auto' | How many slides one move scrolls. auto follows slidesPerPage. |
spacing | string | '1rem' | Space between slides. |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Scroll direction. |
loop | boolean | false | Wrap around at both ends. |
autoplay | boolean | { delay: number } | false | Scroll automatically. The default delay is 4000 ms. |
autoSize | boolean | false | Allow slides with different widths or heights. |
allowMouseDrag | boolean | false | Allow scrolling by dragging with the mouse. |
snapType | 'proximity' | 'mandatory' | 'mandatory' | How strictly scrolling snaps to slides. |
inViewThreshold | number | number[] | 0.6 | How much of a slide must be visible to count as in view. |
padding | string | – | Extra space around the scroll area, so neighbouring slides stay partly visible. |
onAutoplayStatusChange | (details) => void | – | Called when autoplay starts or stops. |
onDragStatusChange | (details) => void | – | Called when dragging starts or stops. |
translations | IntlTranslations | – | Localized labels used for accessibility. |
ids | Partial<{ root, item, itemGroup, ... }> | – | Custom element ids, useful for composition. |
asChild | boolean | – | Render the child element instead of the default one. |
unstyled | boolean | – | Remove the component's styles. |
Item
| Prop | Type | Default | Description |
|---|---|---|---|
index* | number | – | The position of this slide. |
snapAlign | 'start' | 'center' | 'end' | 'start' | Where the slide stops when snapping. |
Indicator
| Prop | Type | Default | Description |
|---|---|---|---|
index* | number | – | The slide this indicator jumps to. |
unstyled | boolean | – | Remove the dot styling, to render your own content such as a thumbnail. |
* required. Style props and HTML attributes are accepted as well.
Accessibility
- The root is exposed as a carousel region and each slide as a slide group, so screen readers announce the position (for example 1 of 5).
- Icon-only triggers have no text. Always give
Carousel.PrevTriggerandCarousel.NextTriggerbuttons anaria-label. - Autoplay moves content on its own. Offer
Carousel.AutoplayTriggerso people can pause it, and avoid autoplay for anything important.
Notes
- In
Carousel.AutoplayIndicator,pausedis what shows while the carousel is playing (the button will pause it) andplayshows while it is paused. - Slides use CSS scroll snapping, not transforms. Anything that scrolls natively (trackpad, touch, keyboard) works with it.
Carousel.Indicatorsis a shortcut. UseCarousel.IndicatorGroupplusCarousel.Indicatorwhen each indicator needs custom content.- Images inside slides should set
draggable={false}whenallowMouseDragis on, otherwise the browser starts an image drag.
Created by @indokudev