Carousel
Virtualized carousel driven by data.
Installation
npx axiom add carouselpnpm dlx axiom add carouselyarn dlx axiom add carouselbun x axiom add carouselInstalls: react-native-reanimated.
Scrolling, snapping, autoplay and the imperative API live in use-carousel.ts, on top of a virtualized FlatList.
Usage
The API follows the list model: pass data and a render function.
import { Carousel } from "@/components/ui/carousel";
<Carousel
data={stories}
renderItem={({ item }) => <StoryCard story={item} />}
keyExtractor={(item) => item.id}
/>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | T[] | — | 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) => string | index | Stable key per item. |
itemWidth | number | screen width − margins | Width of each item. Smaller than the viewport, the next item peeks in. |
gap | SpacingToken | 3 | Space between items. |
contentInset | SpacingToken | screenMargin | Padding 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. |
pagination | boolean | 'dots' | 'counter' | false | Indicator under the carousel: dots, or 2 / 8. |
index | number | — | Controlled active index. |
onIndexChange | (index: number) => void | — | Called when the carousel settles on a new item. |
loop | boolean | false | Wraps from the last item back to the first. |
autoPlay | number | — | Interval in ms to advance automatically. Paused while the user touches it and when reduce motion is on. |
windowSize | number | 5 | Items rendered around the active one. Others are unmounted to keep long carousels light. |
ref | Ref<CarouselRef> | — | scrollToIndex(index, animated?), next(), prev(). |
accessibilityLabel | string | — | Names the carousel for screen readers, such as "Product photos". |
style | StyleProp<ViewStyle> | — | Extra styles for the container. |
Use cases
Onboarding
Full-width pages with snap="page", dots, and a button that follows the index.
const ref = useRef<CarouselRef>(null);
const [index, setIndex] = useState(0);
<Carousel ref={ref} data={slides} snap="page" pagination="dots" onIndexChange={setIndex} renderItem={renderSlide} />
<Button size="lg" fullWidth onPress={() => (index < slides.length - 1 ? ref.current?.next() : finish())}>
{index < slides.length - 1 ? 'Next' : 'Get started'}
</Button>Product gallery
Photos with a counter instead of dots when there are many.
<Carousel
data={product.photos}
snap="page"
pagination="counter"
renderItem={({ item }) => <Image source={item} style={styles.photo} />}
/>Stories row
Small round items, free scroll with snap="none", and only nearby items mounted.
<Carousel
data={stories}
itemWidth={64}
gap={3}
snap="none"
windowSize={10}
renderItem={renderStory}
/>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.