Components
Carousel

Carousel

An interactive slideshow component for cycling through elements.

Loading...

Features

  • Native CSS Scroll Snap integration for smooth, performant animations
  • Flexible orientation support (horizontal and vertical layouts)
  • Customizable slide alignment (start, center, or end positions)
  • Multi-slide display capabilities for complex layouts
  • Automatic playback with configurable looping behavior
  • Adjustable slide spacing and gap controls

Anatomy

To set up the carousel correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

Examples

Learn how to use the Carousel component in your project. Let's take a look at the most basic example:

import { Carousel } from '@ark-ui/react/carousel'

export const Basic = () => {
  const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)

  return (
    <Carousel.Root defaultPage={0} slideCount={images.length}>
      <Carousel.Control>
        <Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
        <Carousel.NextTrigger>Next</Carousel.NextTrigger>
      </Carousel.Control>
      <Carousel.IndicatorGroup>
        {images.map((_, index) => (
          <Carousel.Indicator key={index} index={index} />
        ))}
      </Carousel.IndicatorGroup>
      <Carousel.ItemGroup>
        {images.map((image, index) => (
          <Carousel.Item key={index} index={index}>
            <img src={image} alt={`Slide ${index}`} />
          </Carousel.Item>
        ))}
      </Carousel.ItemGroup>
    </Carousel.Root>
  )
}

Controlled

To create a controlled Carousel component, you can manage the state of the carousel using the page prop and update it when the onPageChange event handler is called:

import { Carousel } from '@ark-ui/react/carousel'
import { useState } from 'react'

const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)

export const Controlled = () => {
  const [page, setPage] = useState(0)

  return (
    <Carousel.Root slideCount={images.length} page={page} onPageChange={(e) => setPage(e.page)}>
      <Carousel.Control>
        <Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
        <Carousel.NextTrigger>Next</Carousel.NextTrigger>
      </Carousel.Control>
      <Carousel.IndicatorGroup>
        {images.map((_, index) => (
          <Carousel.Indicator key={index} index={index} />
        ))}
      </Carousel.IndicatorGroup>
      <Carousel.ItemGroup>
        {images.map((image, index) => (
          <Carousel.Item key={index} index={index}>
            <img src={image} alt={`Slide ${index}`} />
          </Carousel.Item>
        ))}
      </Carousel.ItemGroup>
    </Carousel.Root>
  )
}

Root Provider

Use the useCarousel hook to create the carousel store and pass it to the Carousel.RootProvider component. This allows you to have maximum control over the carousel programmatically.

import { Carousel, useCarousel } from '@ark-ui/react/carousel'

const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)

export const RootProvider = () => {
  const carousel = useCarousel({ slideCount: images.length })
  return (
    <>
      <button onClick={() => carousel.scrollToIndex(2)}>Scroll to #3</button>
      <Carousel.RootProvider value={carousel}>
        <Carousel.Control>
          <Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
          <Carousel.NextTrigger>Next</Carousel.NextTrigger>
        </Carousel.Control>
        <Carousel.IndicatorGroup>
          {images.map((_, index) => (
            <Carousel.Indicator key={index} index={index} />
          ))}
        </Carousel.IndicatorGroup>
        <Carousel.ItemGroup>
          {images.map((image, index) => (
            <Carousel.Item key={index} index={index}>
              <img src={image} alt={`Slide ${index}`} />
            </Carousel.Item>
          ))}
        </Carousel.ItemGroup>
      </Carousel.RootProvider>
    </>
  )
}

If you're using the Carousel.RootProvider component, you don't need to use the Carousel.Root component.

Autoplay

Pass the autoplay and loop props to Carousel.Root to make the carousel play automatically.

Note: Adding loop ensures the carousel keeps going after the last slide.

import { Carousel } from '@ark-ui/react/carousel'

const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)

export const Autoplay = () => {
  return (
    <Carousel.Root slideCount={images.length} autoplay loop>
      <Carousel.Control>
        <Carousel.AutoplayTrigger>
          <Carousel.Context>{({ isPlaying }) => (isPlaying ? 'Pause' : 'Play')}</Carousel.Context>
        </Carousel.AutoplayTrigger>
      </Carousel.Control>
      <Carousel.IndicatorGroup>
        {images.map((_, index) => (
          <Carousel.Indicator key={index} index={index} />
        ))}
      </Carousel.IndicatorGroup>
      <Carousel.ItemGroup>
        {images.map((image, index) => (
          <Carousel.Item key={index} index={index}>
            <img src={image} alt={`Slide ${index}`} />
          </Carousel.Item>
        ))}
      </Carousel.ItemGroup>
    </Carousel.Root>
  )
}

Pause on Hover

This feature isn't built-in, but you can use the play() and pause() methods from Carousel.Context to implement pause on hover.

Add the autoplay and loop props to Carousel.Root, then attach onPointerOver and onPointerLeave handlers to Carousel.ItemGroup that call api.pause() and api.play() respectively.

import { Carousel } from '@ark-ui/react/carousel'

const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)

export const PauseOnHover = () => {
  return (
    <Carousel.Root slideCount={images.length} autoplay loop>
      <Carousel.Control>
        <Carousel.Context>{({ isPlaying }) => `Autoplay is: ${isPlaying ? 'playing' : 'paused'}`}</Carousel.Context>
      </Carousel.Control>
      <Carousel.Context>
        {(api) => (
          <Carousel.ItemGroup onPointerOver={() => api.pause()} onPointerLeave={() => api.play()}>
            {images.map((image, index) => (
              <Carousel.Item key={index} index={index}>
                <img src={image} alt={`Slide ${index}`} />
              </Carousel.Item>
            ))}
          </Carousel.ItemGroup>
        )}
      </Carousel.Context>
      <Carousel.IndicatorGroup>
        {images.map((_, index) => (
          <Carousel.Indicator key={index} index={index} />
        ))}
      </Carousel.IndicatorGroup>
    </Carousel.Root>
  )
}

Custom Indicators

Replace default indicator dots with custom content by wrapping Carousel.IndicatorGroup in Carousel.Context. Use api.page to determine the active indicator and render image thumbnails for each slide:

import { Carousel } from '@ark-ui/react/carousel'

export const CustomIndicator = () => {
  const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)

  return (
    <Carousel.Root defaultPage={0} slideCount={images.length}>
      <Carousel.Control>
        <Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
        <Carousel.NextTrigger>Next</Carousel.NextTrigger>
      </Carousel.Control>
      <Carousel.ItemGroup>
        {images.map((image, index) => (
          <Carousel.Item key={index} index={index}>
            <img src={image} alt={`Slide ${index}`} style={{ width: '100%', height: '300px', objectFit: 'cover' }} />
          </Carousel.Item>
        ))}
      </Carousel.ItemGroup>
      <Carousel.Context>
        {(api) => (
          <Carousel.IndicatorGroup style={{ display: 'flex', gap: '8px', marginTop: '16px' }}>
            {images.map((image, index) => (
              <Carousel.Indicator key={index} index={index}>
                <img
                  src={image}
                  alt={`Thumbnail ${index}`}
                  style={{
                    width: '60px',
                    height: '40px',
                    objectFit: 'cover',
                    cursor: 'pointer',
                    border: api.page === index ? '2px solid #0066ff' : '2px solid transparent',
                    borderRadius: '4px',
                    opacity: api.page === index ? 1 : 0.6,
                    transition: 'all 0.2s',
                  }}
                />
              </Carousel.Indicator>
            ))}
          </Carousel.IndicatorGroup>
        )}
      </Carousel.Context>
    </Carousel.Root>
  )
}

Vertical Orientation

Set the orientation="vertical" prop on Carousel.Root to change the carousel from horizontal to vertical scrolling. This is useful for vertical galleries or content feeds.

import { Carousel } from '@ark-ui/react/carousel'

export const Vertical = () => {
  const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)

  return (
    <Carousel.Root defaultPage={0} orientation="vertical" slideCount={images.length}>
      <Carousel.Control>
        <Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
        <Carousel.NextTrigger>Next</Carousel.NextTrigger>
      </Carousel.Control>
      <Carousel.IndicatorGroup>
        {images.map((_, index) => (
          <Carousel.Indicator key={index} index={index} />
        ))}
      </Carousel.IndicatorGroup>
      <Carousel.ItemGroup>
        {images.map((image, index) => (
          <Carousel.Item key={index} index={index}>
            <img src={image} alt={`Slide ${index}`} />
          </Carousel.Item>
        ))}
      </Carousel.ItemGroup>
    </Carousel.Root>
  )
}

Dynamic Slides

Manage slides dynamically by storing them in state and syncing the carousel page. Pass the page prop and onPageChange handler to Carousel.Root, and update slideCount when slides are added or removed. This demonstrates bidirectional state synchronization between your component state and the carousel.

import { Carousel } from '@ark-ui/react/carousel'
import { useState } from 'react'

export const DynamicSlides = () => {
  const [slides, setSlides] = useState([0, 1, 2, 3, 4])
  const [page, setPage] = useState(0)

  const addSlide = () => {
    setSlides((prevSlides) => {
      const max = Math.max(...prevSlides)
      return [...prevSlides, max + 1]
    })
  }

  return (
    <div>
      <Carousel.Root slideCount={slides.length} page={page} onPageChange={(details) => setPage(details.page)}>
        <Carousel.Control>
          <Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
          <Carousel.NextTrigger>Next</Carousel.NextTrigger>
        </Carousel.Control>
        <Carousel.IndicatorGroup>
          {slides.map((_, index) => (
            <Carousel.Indicator key={index} index={index} />
          ))}
        </Carousel.IndicatorGroup>
        <Carousel.ItemGroup>
          {slides.map((slide, index) => (
            <Carousel.Item key={index} index={index}>
              <div
                style={{
                  display: 'flex',
                  alignItems: 'center',
                  justifyContent: 'center',
                  width: '100%',
                  height: '300px',
                  backgroundColor: `hsl(${(slide * 60) % 360}, 70%, 60%)`,
                  color: 'white',
                  fontSize: '24px',
                  fontWeight: 'bold',
                  borderRadius: '8px',
                }}
              >
                Slide {slide}
              </div>
            </Carousel.Item>
          ))}
        </Carousel.ItemGroup>
      </Carousel.Root>
      <button onClick={addSlide} style={{ marginTop: '16px', padding: '8px 16px', cursor: 'pointer' }}>
        Add Slide
      </button>
    </div>
  )
}

Scroll to Slide

Use Carousel.Context to access the carousel API and call api.scrollToIndex(index) to programmatically navigate to a specific slide. This is useful for creating custom navigation or jump-to-slide functionality.

import { Carousel } from '@ark-ui/react/carousel'

export const ScrollTo = () => {
  return (
    <Carousel.Root slideCount={5}>
      <Carousel.Context>
        {(api) => <button onClick={() => api.scrollToIndex(3)}>Go to slide 4</button>}
      </Carousel.Context>
      <Carousel.ItemGroup>
        {Array.from({ length: 5 }, (_, index) => (
          <Carousel.Item key={index} index={index}>
            <div
              style={{
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                width: '100%',
                height: '300px',
                backgroundColor: '#f0f0f0',
                fontSize: '24px',
                fontWeight: 'bold',
                borderRadius: '8px',
              }}
            >
              Slide {index + 1}
            </div>
          </Carousel.Item>
        ))}
      </Carousel.ItemGroup>
      <Carousel.Control>
        <Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
        <Carousel.NextTrigger>Next</Carousel.NextTrigger>
      </Carousel.Control>
      <Carousel.IndicatorGroup>
        {Array.from({ length: 5 }, (_, index) => (
          <Carousel.Indicator key={index} index={index} />
        ))}
      </Carousel.IndicatorGroup>
    </Carousel.Root>
  )
}

Slides Per Page

Display multiple slides simultaneously by setting the slidesPerPage prop on Carousel.Root. Use api.pageSnapPoints from Carousel.Context to render the correct number of indicators based on pages rather than individual slides. Add the spacing prop to control the gap between slides.

import { Carousel } from '@ark-ui/react/carousel'

export const SlidesPerPage = () => {
  const slides = Array.from({ length: 5 }, (_, i) => i)

  return (
    <Carousel.Root slideCount={slides.length} slidesPerPage={2} spacing="20px">
      <Carousel.Control>
        <Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
        <Carousel.NextTrigger>Next</Carousel.NextTrigger>
      </Carousel.Control>
      <Carousel.ItemGroup>
        {slides.map((_, index) => (
          <Carousel.Item key={index} index={index}>
            <div
              style={{
                width: '100%',
                height: '300px',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                background: '#f0f0f0',
              }}
            >
              Slide {index + 1}
            </div>
          </Carousel.Item>
        ))}
      </Carousel.ItemGroup>
      <Carousel.Context>
        {(api) => (
          <Carousel.IndicatorGroup>
            {api.pageSnapPoints.map((_, index) => (
              <Carousel.Indicator key={index} index={index} />
            ))}
          </Carousel.IndicatorGroup>
        )}
      </Carousel.Context>
    </Carousel.Root>
  )
}

API Reference

Root

PropDefaultType
slideCount
number

The total number of slides. Useful for SSR to render the initial ating the snap points.

allowMouseDragfalse
boolean

Whether to allow scrolling via dragging with mouse

asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
autoplayfalse
boolean | { delay: number }

Whether to scroll automatically. The default delay is 4000ms.

defaultPage0
number

The initial page to scroll to when rendered. Use when you don't need to control the page of the carousel.

ids
Partial<{ root: string item: (index: number) => string itemGroup: string nextTrigger: string prevTrigger: string indicatorGroup: string indicator: (index: number) => string }>

The ids of the elements in the carousel. Useful for composition.

inViewThreshold0.6
number | number[]

The threshold for determining if an item is in view.

loopfalse
boolean

Whether the carousel should loop around.

onAutoplayStatusChange
(details: AutoplayStatusDetails) => void

Function called when the autoplay status changes.

onDragStatusChange
(details: DragStatusDetails) => void

Function called when the drag status changes.

onPageChange
(details: PageChangeDetails) => void

Function called when the page changes.

orientation'horizontal'
'horizontal' | 'vertical'

The orientation of the element.

padding
string

Defines the extra space added around the scrollable area, enabling nearby items to remain partially in view.

page
number

The controlled page of the carousel.

slidesPerMove'auto'
number | 'auto'

The number of slides to scroll at a time. When set to `auto`, the number of slides to scroll is determined by the `slidesPerPage` property.

slidesPerPage1
number

The number of slides to show at a time.

snapType'mandatory'
'proximity' | 'mandatory'

The snap type of the item.

spacing'0px'
string

The amount of space between items.

translations
IntlTranslations

The localized messages to use.

CSS VariableDescription
--slides-per-pageThe number of slides visible per page
--slide-spacingThe spacing between slides
--slide-item-sizeThe calculated size of each slide item
Data AttributeValue
[data-scope]carousel
[data-part]root
[data-orientation]The orientation of the carousel

AutoplayTrigger

PropDefaultType
asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]carousel
[data-part]autoplay-trigger
[data-orientation]The orientation of the autoplaytrigger
[data-pressed]Present when pressed

Control

PropDefaultType
asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]carousel
[data-part]control
[data-orientation]The orientation of the control

IndicatorGroup

PropDefaultType
asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]carousel
[data-part]indicator-group
[data-orientation]The orientation of the indicatorgroup

Indicator

PropDefaultType
index
number

The index of the indicator.

asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
readOnlyfalse
boolean

Whether the indicator is read only.

Data AttributeValue
[data-scope]carousel
[data-part]indicator
[data-orientation]The orientation of the indicator
[data-index]The index of the item
[data-readonly]Present when read-only
[data-current]Present when current

ItemGroup

PropDefaultType
asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]carousel
[data-part]item-group
[data-orientation]The orientation of the item
[data-dragging]Present when in the dragging state

Item

PropDefaultType
index
number

The index of the item.

asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
snapAlign'start'
'center' | 'end' | 'start'

The snap alignment of the item.

Data AttributeValue
[data-scope]carousel
[data-part]item
[data-index]The index of the item
[data-inview]Present when in viewport
[data-orientation]The orientation of the item

NextTrigger

PropDefaultType
asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]carousel
[data-part]next-trigger
[data-orientation]The orientation of the nexttrigger

PrevTrigger

PropDefaultType
asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]carousel
[data-part]prev-trigger
[data-orientation]The orientation of the prevtrigger

RootProvider

PropDefaultType
value
UseCarouselReturn

asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.

Accessibility

Complies with the Carousel WAI-ARIA design pattern.