Carousel
An interactive slideshow component for cycling through elements.
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>
<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>
)
}
import { Carousel } from '@ark-ui/solid/carousel'
import { Index } from 'solid-js'
const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)
export const Basic = () => {
return (
<Carousel.Root>
<Carousel.Control>
<Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
<Carousel.NextTrigger>Next</Carousel.NextTrigger>
</Carousel.Control>
<Carousel.IndicatorGroup>
<Index each={images}>{(_, index) => <Carousel.Indicator index={index} />}</Index>
</Carousel.IndicatorGroup>
<Carousel.ItemGroup>
<Index each={images}>
{(image, index) => (
<Carousel.Item index={index}>
<img src={image()} alt={`Slide ${index}`} />
</Carousel.Item>
)}
</Index>
</Carousel.ItemGroup>
</Carousel.Root>
)
}
<script setup lang="ts">
import { Carousel } from '@ark-ui/vue/carousel'
const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)
</script>
<template>
<Carousel.Root>
<Carousel.Control>
<Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
<Carousel.NextTrigger>Next</Carousel.NextTrigger>
</Carousel.Control>
<Carousel.IndicatorGroup>
<Carousel.Indicator v-for="(_, idx) in images" :key="idx" :index="idx" />
</Carousel.IndicatorGroup>
<Carousel.ItemGroup>
<Carousel.Item v-for="(image, idx) in images" :key="idx" :index="idx">
<img
:src="image"
alt=""
:style="{ height: '300px', width: '100%', objectFit: 'cover' }"
/>
</Carousel.Item>
</Carousel.ItemGroup>
</Carousel.Root>
</template>
Controlled Carousel
To create a controlled Carousel component, you can manage the state of the carousel using the
index
prop and update it when the onIndexChange
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 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>
)
}
import { Carousel } from '@ark-ui/solid/carousel'
import { Index, createSignal } from 'solid-js'
const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)
export const Controlled = () => {
const [page, setPage] = createSignal(0)
return (
<>
<Carousel.Root page={page()} onPageChange={(details) => setPage(details.page)}>
<Carousel.Control>
<Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
<Carousel.NextTrigger>Next</Carousel.NextTrigger>
</Carousel.Control>
<Carousel.IndicatorGroup>
<Index each={images}>{(_, index) => <Carousel.Indicator index={index} />}</Index>
</Carousel.IndicatorGroup>
<Carousel.ItemGroup>
<Index each={images}>
{(image, index) => (
<Carousel.Item index={index}>
<img src={image()} alt={`Slide ${index}`} />
</Carousel.Item>
)}
</Index>
</Carousel.ItemGroup>
</Carousel.Root>
</>
)
}
<script setup lang="ts">
import { Carousel } from '@ark-ui/vue/carousel'
import { ref } from 'vue'
const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)
const page = ref(0)
</script>
<template>
<Carousel.Root v-model:page="page">
<Carousel.Control>
<Carousel.PrevTrigger>Previous</Carousel.PrevTrigger>
<Carousel.NextTrigger>Next</Carousel.NextTrigger>
</Carousel.Control>
<Carousel.IndicatorGroup>
<Carousel.Indicator v-for="(_, idx) in images" :key="idx" :index="idx" />
</Carousel.IndicatorGroup>
<Carousel.ItemGroup>
<Carousel.Item v-for="(image, idx) in images" :key="idx" :index="idx">
<img :src="image" alt="" />
</Carousel.Item>
</Carousel.ItemGroup>
</Carousel.Root>
</template>
Autoplay
The Carousel can play automatically. Just add the autoplay
prop. You'll probably want to add loop
too, so it 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 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>
)
}
import { Carousel } from '@ark-ui/solid/carousel'
import { Index } from 'solid-js'
const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)
export const Autoplay = () => {
return (
<Carousel.Root autoplay loop>
<Carousel.Control>
<Carousel.AutoplayTrigger>
<Carousel.Context>
{(carousel) => (carousel().isPlaying ? 'Pause' : 'Play')}
</Carousel.Context>
</Carousel.AutoplayTrigger>
</Carousel.Control>
<Carousel.IndicatorGroup>
<Index each={images}>{(_, index) => <Carousel.Indicator index={index} />}</Index>
</Carousel.IndicatorGroup>
<Carousel.ItemGroup>
<Index each={images}>
{(image, index) => (
<Carousel.Item index={index}>
<img src={image()} alt={`Slide ${index}`} />
</Carousel.Item>
)}
</Index>
</Carousel.ItemGroup>
</Carousel.Root>
)
}
<script setup lang="ts">
import { Carousel } from '@ark-ui/vue/carousel'
const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)
</script>
<template>
<Carousel.Root autoplay loop>
<Carousel.Control>
<Carousel.AutoplayTrigger>
<Carousel.Context v-slot="context">
{{ context.isPlaying ? 'Pause' : 'Play' }}
</Carousel.Context>
</Carousel.AutoplayTrigger>
</Carousel.Control>
<Carousel.IndicatorGroup>
<Carousel.Indicator v-for="(_, idx) in images" :key="idx" :index="idx" />
</Carousel.IndicatorGroup>
<Carousel.ItemGroup>
<Carousel.Item v-for="(image, idx) in images" :key="idx" :index="idx">
<img :src="image" alt="" />
</Carousel.Item>
</Carousel.ItemGroup>
</Carousel.Root>
</template>
Using the Root Provider
The RootProvider
sets up carousel context using the useCarousel
hook, enabling external access to its state and methods.
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()
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>
</>
)
}
import { Carousel, useCarousel } from '@ark-ui/solid/carousel'
import { Index } from 'solid-js'
const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)
export const RootProvider = () => {
const carousel = useCarousel()
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>
<Index each={images}>{(_, index) => <Carousel.Indicator index={index} />}</Index>
</Carousel.IndicatorGroup>
<Carousel.ItemGroup>
<Index each={images}>
{(image, index) => (
<Carousel.Item index={index}>
<img src={image()} alt={`Slide ${index}`} />
</Carousel.Item>
)}
</Index>
</Carousel.ItemGroup>
</Carousel.RootProvider>
</>
)
}
<script setup lang="ts">
import { Carousel, useCarousel } from '@ark-ui/vue/carousel'
const images = Array.from({ length: 5 }, (_, i) => `https://picsum.photos/seed/${i + 1}/500/300`)
const carousel = useCarousel()
</script>
<template>
<button @click="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>
<Carousel.Indicator v-for="(_, idx) in images" :key="idx" :index="idx" />
</Carousel.IndicatorGroup>
<Carousel.ItemGroup>
<Carousel.Item v-for="(image, idx) in images" :key="idx" :index="idx">
<img :src="image" alt="" />
</Carousel.Item>
</Carousel.ItemGroup>
</Carousel.RootProvider>
</template>
If you're using the
RootProvider
component, you don't need to use theRoot
component.
API Reference
Root
Prop | Default | Type |
---|---|---|
allowMouseDrag | false | 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. | |
autoplay | false | boolean | { delay: number } Whether to scroll automatically. The default delay is 4000ms. |
defaultPage | number The initial page of the carousel when it is first rendered. Use this when you do not need to control the state 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. | |
inViewThreshold | 0.6 | number | number[] The threshold for determining if an item is in view. |
loop | false | 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' | Orientation 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 index of the active page. | |
slideCount | number The total number of slides. Useful for SSR to render the initial ating the snap points. | |
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. |
slidesPerPage | 1 | 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. |
Data Attribute | Value |
---|---|
[data-scope] | carousel |
[data-part] | root |
[data-orientation] | The orientation of the carousel |
AutoplayTrigger
Prop | Default | Type |
---|---|---|
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 Attribute | Value |
---|---|
[data-scope] | carousel |
[data-part] | autoplay-trigger |
[data-orientation] | The orientation of the autoplaytrigger |
[data-pressed] | Present when pressed |
Control
Prop | Default | Type |
---|---|---|
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 Attribute | Value |
---|---|
[data-scope] | carousel |
[data-part] | control |
[data-orientation] | The orientation of the control |
IndicatorGroup
Prop | Default | Type |
---|---|---|
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 Attribute | Value |
---|---|
[data-scope] | carousel |
[data-part] | indicator-group |
[data-orientation] | The orientation of the indicatorgroup |
Indicator
Prop | Default | Type |
---|---|---|
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. | |
readOnly | false | boolean Whether the indicator is read only. |
Data Attribute | Value |
---|---|
[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
Prop | Default | Type |
---|---|---|
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 Attribute | Value |
---|---|
[data-scope] | carousel |
[data-part] | item-group |
[data-orientation] | The orientation of the item |
[data-dragging] | Present when in the dragging state |
Item
Prop | Default | Type |
---|---|---|
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 Attribute | Value |
---|---|
[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
Prop | Default | Type |
---|---|---|
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 Attribute | Value |
---|---|
[data-scope] | carousel |
[data-part] | next-trigger |
[data-orientation] | The orientation of the nexttrigger |
PrevTrigger
Prop | Default | Type |
---|---|---|
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 Attribute | Value |
---|---|
[data-scope] | carousel |
[data-part] | prev-trigger |
[data-orientation] | The orientation of the prevtrigger |
RootProvider
Prop | Default | Type |
---|---|---|
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.