Carousel
Swipeable slides on one fractional position — peek, loop, vertical, coverflow, autoplay, dots and arrows.
Installation
Carousel ships with the library — install it once, then import:
import { Carousel } from "@delacour/react-native-ui/carousel";Or copy the source into your project, to own and edit it:
bunx delacour@latest add carouselUsage
import { Carousel } from "@/components/ui/carousel";
import { Image, View } from "react-native";
const PHOTOS = [
{ id: "harbour", title: "Harbour at dawn" },
{ id: "ridge", title: "Ridge line" },
{ id: "orchard", title: "Orchard rows" },
];
export default function Example() {
return (
<View className="flex-1 justify-center p-4">
<Carousel accessibilityLabel="Featured">
<Carousel.Content aspectRatio={4 / 3}>
{PHOTOS.map((photo) => (
<Carousel.Item key={photo.id}>
<Image className="size-full" source={{ uri: `https://picsum.photos/seed/${photo.id}/800/600` }} />
<Carousel.Caption>{photo.title}</Carousel.Caption>
</Carousel.Item>
))}
</Carousel.Content>
<Carousel.Dots />
</Carousel>
</View>
);
}Slides are Carousel.Items, direct children of Carousel.Content, and their source order is the
order of record. A wrapper around an Item hides it, and a development warning says so. It is built
for a handful of hand-written slides — a feed belongs in a list.
Peek and centring
itemSize, in points, smaller than the viewport centres the active slide and lets its neighbours show
either side. Omitted, each slide is the viewport's length.

<Carousel accessibilityLabel="Trees" itemSize={260}>
<Carousel.Content aspectRatio={1.2}>{slides}</Carousel.Content>
</Carousel>Looping
loop leads the last slide back to the first, both ways, with no clones and no jump. previous,
next and scrollTo take the short way round.
<Carousel accessibilityLabel="Times of day" loop>
<Carousel.Content aspectRatio={16 / 9}>{slides}</Carousel.Content>
</Carousel>Orientation
orientation="vertical" pages up and down. A vertical viewport takes its height from a class —
aspectRatio is horizontal only. The controls row and the dots turn into columns.

<Carousel accessibilityLabel="Stories" className="flex-row" orientation="vertical">
<Carousel.Content className="h-96 flex-1">{slides}</Carousel.Content>
<Carousel.Dots />
</Carousel>Coverflow
variant="coverflow" turns, shrinks and fades each slide by its distance from the centre. Pair it
with itemSize so the neighbours show. Under reduce motion it draws as a plain track.
<Carousel accessibilityLabel="Albums" itemSize={200} variant="coverflow">
<Carousel.Content aspectRatio={1.6}>{slides}</Carousel.Content>
</Carousel>Autoplay
autoplay advances every autoplayInterval milliseconds (default 4000).
<Carousel accessibilityLabel="Highlights" autoplay autoplayInterval={3000} loop>
<Carousel.Content aspectRatio={16 / 9}>{slides}</Carousel.Content>
</Carousel>Autoplay stops for good on the first touch, an arrow press or a scrollTo, and at the end of a run
that does not loop. It never runs under reduce motion or while a screen reader is on (WCAG 2.2.2),
and it pauses while the app is in the background.
Controls and dots
Carousel.Controls lays out Previous · Dots · Next. The arrows disable at the ends unless the
carousel loops. Dots are decoration — not tappable and hidden from assistive tech; past maxDots
(default 7) they show a sliding window. tone="overlay" draws light dots for sitting on a photo.
<Carousel accessibilityLabel="Tour">
<Carousel.Content aspectRatio={1.6}>{slides}</Carousel.Content>
<Carousel.Controls>
<Carousel.Previous />
<Carousel.Dots />
<Carousel.Next />
</Carousel.Controls>
</Carousel>Controlled
Pass index with onIndexChange to own the slide. onIndexChange fires once per swipe, from the
release — never per frame. A change you reject springs back.
const [index, setIndex] = useState(0);
<Carousel accessibilityLabel="Colours" index={index} onIndexChange={setIndex}>
<Carousel.Content aspectRatio={2}>{slides}</Carousel.Content>
</Carousel>For a custom control inside the carousel, useCarousel() returns the index, the count and the
actions.
function Counter() {
const { index, count } = useCarousel();
return <Text>{`${index + 1} / ${count}`}</Text>;
}Inside a scroll view
The pan claims its own axis and yields the other, so a horizontal carousel sits inside a vertical
Screen.ScrollArea and the page still scrolls. A Pressable inside a slide still taps.
API Reference
Anatomy
| Part | What it is |
|---|---|
Carousel | Owns the index, the options and autoplay |
Carousel.Content | The clipped viewport, the pan and the adjustable element |
Carousel.Item | One slide, absolutely placed and moved by transform |
Carousel.Caption | A caption on a scrim at the slide's foot, visible only while active |
Carousel.Dots | One dot per slide, the active one a pill |
Carousel.Previous / Carousel.Next | Icon buttons, disabled at the ends unless looping |
Carousel.Controls | A row for the arrows and dots — a column when vertical |
Carousel
Prop
Type
Carousel.Content
Prop
Type
Carousel.Item
Prop
Type
Carousel.Dots
Prop
Type
Carousel.Previous / Carousel.Next
Take every Button prop but onPress. children replaces the chevron; accessibilityLabel defaults
to "Previous slide" / "Next slide".
useCarousel
Prop
Type
Accessibility
Carousel.Content is an adjustable element: a VoiceOver or TalkBack swipe up or down moves a slide
and reads "2 of 5". Off-screen slides are hidden from assistive tech unless the carousel peeks. Under
reduce motion the settle is a short timing instead of a spring, coverflow flattens, and autoplay is
off.

