Carousel

Organism
A horizontally paged slide viewer: swipe (or use the prev/next arrows and the dot indicators) to move one slide at a time.

Playground

Every control sets a real prop of CarouselProps. The JSX below updates as you go.
Slide 1
Props
index
number
0
showArrows
boolean
showDots
boolean
loop
boolean
JSX · CarouselProps
<Carousel index={0} showArrows showDots />

Variants

4 examples from src/organisms/carousel/carousel.md, each rendered live from its own JSX.
Featured
Arrows hidden (dots only)
Hide the prev/next chevrons with showArrows={false}; swipe and the dots still page the carousel, the iOS page-control idiom.
<Carousel
showArrows={false}
items={[
{ key: "a", content: "Featured" },
{ key: "b", content: "Popular" }
]}
/>
Second
Default index
Start on a later slide with defaultIndex; the matching dot reads selected.
<Carousel
defaultIndex={1}
items={[
{ key: "x", content: "First" },
{ key: "y", content: "Second" },
{ key: "z", content: "Third" }
]}
/>
Photos
Arrows and dots everywhere
The chrome defaults are platform-adaptive (web shows both, iOS dots only, Android neither); pass showArrows and showDots to force the full arrows-plus-dots anatomy on every platform.
<Carousel
showArrows
showDots
items={[
{ key: "p", content: "Photos" },
{ key: "q", content: "Albums" },
{ key: "r", content: "Shared" }
]}
/>
Start
Loop
With loop the index wraps: both arrows stay enabled at the ends, and next on the last slide returns to the first.
<Carousel
loop
showArrows
items={[
{ key: "first", content: "Start" },
{ key: "mid", content: "Middle" },
{ key: "last", content: "End" }
]}
/>

Anatomy

Slide 1
1
Viewport
2
Slide
3
Arrow
4
Dots
Dimmed parts appear only in some states or variants.

Props

9 props on CarouselProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
9 props
items *
CarouselItem[]
–
The slides, left to right.
index
number
–
Controlled current slide. Pair with onIndexChange. Omit for uncontrolled.
defaultIndex
number
0
Initial current slide for the uncontrolled case (read once; default 0).
onIndexChange
(index: number) => void
–
Called with the next current index whenever the slide changes.
loop
boolean
false
Wrap from the last slide to the first (and first to last) on arrow nav.
showArrows
boolean
false
Show the prev/next chevron buttons beside the slides. Default is platform-adaptive: on for web, off for iOS + Android (swipe idioms); pass true to opt in.
showDots
boolean
false
Show the centered dot indicators below the slides. Default is platform-adaptive: on for web + iOS (UIPageControl), off for Android M3.
testID
string
–
E2E hook forwarded to the root element.
style
LayoutStyle
–
Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds.

Accessibility and keyboard

Keyboard
ArrowLeft
ArrowRight
Previous · next slide
Tab
Reaches arrows and dots
Semantics
Roles
button
group
States
disabled
selected
ARIA on web
aria-current
aria-disabled
Below the platform minimum, the touch area grows to 44 pt on iOS and 48 dp on Android without moving the layout.
From the docs
On web, Tab visits the carousel in the order it reads: the previous arrow, the slide viewport, the next arrow, then the slide picker (an arrow disabled at the end of a non-looping carousel is skipped). Tab reaches the viewport while its slides overflow it, and the focused viewport draws the theme's focus ring around the slide. Left and Right move between slides, while Home and End reach the first and last. Controls inside a slide keep their own keyboard behavior. The named slide-picker buttons report the current sl
The active dot follows the committed slide (a dot press, the arrows, the keyboard, a controlled index, a finished swipe) in both surface modes; the strip is absent with one slide, with showDots={false}, and by default on Android.

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
Photos
Web
control 10 · field 10 · card 16 · Instrument Sans
Photos
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
Photos
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

<Carousel
items={[
{ key: "one", content: "Slide 1" },
{ key: "two", content: "Slide 2" },
{ key: "three", content: "Slide 3" }
]}
/>
Paging snaps to the viewport width, the current slide drives the dots, and the arrows step the index (clamped, or wrapped when loop). Slides hold any content; pass an items array of { key, content }.
Each slide is itself the card: it paints the surface, rounds the corners and clips its content to them. So pass the slide's content, not a Card. A plain string renders in the slide's own type, inset from its edge; any other node fills the slide inside its 1px edge, so a picture is masked to the slide's shape. A Card nested inside frames the slide twice, and on Android the Material 3 item's 28dp corners cut the Card's own 12dp edge.
The prev and next arrows sit beside the slides, never over them, so nothing near a slide's edge is hidden behind an arrow; the slides narrow by the two arrow gutters instead.

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--bf
--f-dp
--fd
--ft
--gl
--ion
--ion-soft
--ln
--ln2
--sf2
--sf4

Related components