Pre-alphaThe registry and the CLI are not published yet.Roadmap

Carousel

Virtualized carousel driven by data.

Explore

NearbySee all

Installation

npx axiom add carousel
pnpm dlx axiom add carousel
yarn dlx axiom add carousel
bun x axiom add carousel

Installs: react-native-reanimated.

Scrolling, snapping, autoplay and the imperative API live in use-carousel.ts, on top of a virtualized FlatList.

Carousel holds the data and the scroll position. What you see is made of parts you place: Carousel.List for the items, and, only if you want them, Carousel.Pagination and Carousel.AutoPlay. A carousel without dots or autoplay runs no code for them.

Usage

The root follows the list model: pass data and a render function. Carousel.List draws them.

import { Carousel } from "@/components/ui/carousel";

<Carousel
	data={stories}
	renderItem={({ item }) => <StoryCard story={item} />}
	keyExtractor={(item) => item.id}
>
	<Carousel.List />
	<Carousel.Pagination />
</Carousel>;

Props

PropTypeDefaultDescription
dataT[]—Items to render.
renderItem({ item, index, progress }) => ReactElement—Renders one item. progress is a shared value from −1 to 1 as the item moves through the center, for scale or parallax effects.
keyExtractor(item: T, index: number) => stringindexStable key per item.
itemWidthnumberscreen width − marginsWidth of each item. Smaller than the viewport, the next item peeks in.
gapSpacingToken3Space between items.
contentInsetSpacingTokenscreenMarginPadding at the start and end, so the first item aligns with the rest of the screen.
snap'item' | 'page' | 'none''item'item stops on each item, page on every visible page, none scrolls freely.
indexnumber—Controlled active index.
onIndexChange(index: number) => void—Called when the carousel settles on a new item.
hapticHapticKind | false—Haptic played when a swipe lands on another item. Off unless you pass a kind.
refRef<CarouselRef>—scrollToIndex(index, animated?), next(), prev(). next() stops at the last item.
childrenReactNode—Carousel.List and the other parts, in the order you want them.
styleStyleProp<ViewStyle>—Extra styles for the container.

Parts

PartWhat it is
Carousel.ListThe horizontal FlatList. Takes windowSize (items kept mounted around the visible ones, 5), initialNumToRender, accessibilityLabel, style and contentContainerStyle.
Carousel.PaginationOne dot per item, the active one stretched into a pill as the list scrolls. A View: place it above or below the list.
Carousel.AutoPlayRenders nothing. Moves to the next item every interval ms, skips a tick while the list is touched, and stays off with Reduce Motion. loop goes back to the first item.

useCarousel() returns the state of the carousel around it: active, count, scrollX, interval and scrollToIndex. Use it for your own indicator, such as a 2 / 8 counter:

function Counter() {
	const { active, count } = useCarousel();
	return (
		<Text variant="footnote" color="muted">
			{active + 1} / {count}
		</Text>
	);
}

useCarouselAutoPlay({ interval, loop }) is the hook behind Carousel.AutoPlay, for a component of yours inside the carousel.

Haptics

Pass a kind, usually "selection", to play a haptic when a swipe lands on another item. Carousel.AutoPlay and ref.current.next() play nothing. Keep it for carousels the user pages through one item at a time, like an onboarding: on a fast gallery it gets tiring. Nothing plays until your app connects a haptics library: see Haptics.

Use cases

Onboarding

Full-width pages with snap="page", dots, and a button that follows the index.

Never miss a train

We’ll ping you when your platform is announced.

Next

const ref = useRef<CarouselRef>(null);
const [index, setIndex] = useState(0);

<Carousel ref={ref} data={slides} snap="page" onIndexChange={setIndex} renderItem={renderSlide}>
  <Carousel.List />
  <Carousel.Pagination />
</Carousel>
<Button size="lg" fullWidth onPress={() => (index < slides.length - 1 ? ref.current?.next() : finish())}>
  {index < slides.length - 1 ? 'Next' : 'Get started'}
</Button>

Photos with a counter instead of dots when there are many. The counter is the Counter component above, laid over the photo.

3 / 8

Canvas tote
<Carousel
	data={product.photos}
	snap="page"
	renderItem={({ item }) => <Image source={item} style={styles.photo} />}
>
	<Carousel.List accessibilityLabel="Product photos" />
	<View style={styles.counter}>
		<Counter />
	</View>
</Carousel>

Stories row

Small round items, free scroll with snap="none", and only nearby items mounted.

Home

<Carousel data={stories} itemWidth={64} gap={3} snap="none" renderItem={renderStory}>
	<Carousel.List windowSize={10} />
</Carousel>

Guidelines

Keep one main scroll direction per area. A horizontal carousel inside a vertical feed is fine; stacking several scroll directions in the same space is not. See Mobile design guidelines.