# Carousel

> A snapping card carousel that follows the finger 1:1, shrinks and dims the neighbours, stretches its pagination pill with the drag, and turns that pill into a timer when it autoplays.

- Type: Component (data)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/carousel
- Markdown: https://goodui.dev/native/components/carousel.md
- Registry item: https://goodui.dev/native/r/carousel.json
- Dependencies: react-native-gesture-handler, react-native-reanimated
- Keywords: react native carousel, expo carousel, snap carousel, card slider, parallax carousel, pagination dots, autoplay slider, onboarding carousel

## When to use

- A few featured items people browse one at a time: stays, plans, offers, onboarding steps, cards in a wallet.
- Content where a peek of the next card invites a swipe.
- Promotions that rotate on their own (autoplay) but stop as soon as someone touches them.

## When not to use

- Long or searchable collections: use a vertical list or a grid.
- Switching between views of the same data: use segmented-control.
- Content everyone must see: a carousel hides everything but the first card.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/carousel.json
npx expo install --fix
```

The component lands in `components/goodui-native/carousel/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { View } from "react-native";
import { Carousel, CarouselParallax } from "@/components/goodui-native/carousel/carousel";
import { Text } from "@/components/goodui-native/foundation/text";

type Stay = { id: string; title: string; price: string };

export function FeaturedStays({ stays, open }: { stays: Stay[]; open: (s: Stay) => void }) {
  return (
    <Carousel
      data={stays}
      height={200}
      keyExtractor={(s) => s.id}
      getItemLabel={(s) => s.title}
      accessibilityLabel="Featured stays"
      onItemPress={(s) => open(s)}
      renderItem={({ item }) => (
        <View style={{ flex: 1, justifyContent: "flex-end", padding: 16 }}>
          <Text size="title3" weight="medium">{item.title}</Text>
          <Text tone="secondary">{item.price} a night</Text>
        </View>
      )}
    />
  );
}
```

## Examples

### Autoplay with a parallax background

```tsx
<Carousel
  data={offers}
  autoplay={5000}
  height={180}
  renderItem={({ item }) => (
    <>
      <CarouselParallax>
        <Image source={item.image} style={{ flex: 1 }} />
      </CarouselParallax>
      <Text style={{ margin: 16 }}>{item.title}</Text>
    </>
  )}
/>
```

### Controlled, full width, flat

```tsx
const [step, setStep] = useState(0);

<Carousel data={steps} index={step} onIndexChange={setStep} itemWidth={1} gap={0} variant="flat" renderItem={...} />
```

## API reference

### Carousel

`import { Carousel } from "@/components/goodui-native/carousel/carousel";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` (required) | `T[]` | – | Items, one card each. Built for a handful to a few dozen cards. |
| `renderItem` (required) | `(info: { item: T; index: number }) => ReactNode` | – | Renders a card. Do not put Pressables inside: use `onItemPress`, which works with dragging on every platform. |
| `keyExtractor` | `(item: T, index: number) => string` | – | Stable key per item. Defaults to the index. |
| `itemWidth` | `number` | `0.84` | Card width as a fraction of the carousel width, 0.5 to 1. Neighbours peek in the rest. Default 0.84. |
| `gap` | `number` | `12` | Space between cards in points. Default 12. |
| `height` | `number` | – | Card height in points. Omit to size cards by their content. |
| `variant` | `"scale" \| "flat"` | `"scale"` | `scale` shrinks and dims the neighbours as they leave the center; `flat` keeps every card the same. |
| `index` | `number` | – | The centered card (controlled). Pair with `onIndexChange`. |
| `defaultIndex` | `number` | `0` | The first centered card (uncontrolled). Default 0. |
| `onIndexChange` | `(index: number) => void` | – | Called when a different card settles in the center. |
| `autoplay` | `boolean \| number` | `false` | Advance on a timer: `true` for every 4 s, or a number of milliseconds. Pauses while touched or hovered, off under reduced motion. |
| `pagination` | `boolean` | `true` | Pill pagination under the cards. The active pill stretches and, with autoplay, fills with the timer. Default `true`. |
| `onItemPress` | `(item: T, index: number) => void` | – | Called when the centered card is tapped. Tapping a neighbour brings it to the center instead. |
| `haptic` | `HapticName \| false` | `"select"` | Haptic when a swipe or a pill changes the card. `false` turns it off. |
| `getItemLabel` | `(item: T, index: number) => string` | – | Short description of a card for screen readers, for example its title. |
| `accessibilityLabel` | `string` | – | Name of the carousel, for example "Featured stays". |
| `labels` | `Partial<CarouselLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

### CarouselParallax

`import { CarouselParallax } from "@/components/goodui-native/carousel/carousel";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` | – | A background layer, usually an image or a color field, that fills the card. |
| `strength` | `number` | `0.18` | How far the layer lags behind the card, as a fraction of the card width. Default 0.18. |

## Accessibility

- The carousel is one adjustable element: swipe up or down with a screen reader to move between cards.
- Its value reads '2 of 5' plus getItemLabel for the centered card; with onItemPress, activating it presses that card.
- Every pagination pill is a button labelled 'Go to card n' with a 44 pt tall touch target.
- Autoplay is off under reduced motion and pauses while a finger or pointer rests on the cards.
- On the web it is a focusable slider with aria-valuenow (card number) and aria-valuetext; arrow keys move between cards and Enter presses the centered card.

## Motion

- Cards follow the finger 1:1 and settle on the smooth spring with the release velocity; a fling moves one card at most.
- Past the first or last card the drag meets growing resistance.
- variant='scale' shrinks neighbours to 0.9 and dims them as they leave the center, continuously with the drag.
- The pagination pill stretches from the old card to the new one with the drag; with autoplay the active pill fills over the interval.
- CarouselParallax layers lag behind their card by a fraction of its width.
- Reduced motion: release settles on a short timing curve, programmatic changes jump, parallax stays still and autoplay is off.

## Haptics

- select when a swipe, a tap on a neighbour or a pill changes the card (configurable with the haptic prop); none for autoplay

## Performance

- Position, scale, dimming, pills and parallax run on the UI thread from one shared value; React renders only when the centered card changes.
- Every card is rendered: keep it to a few dozen cards.

## Notes for AI

- Do not put Pressable or buttons inside cards: they swallow the drag on the web. Use onItemPress for the centered card.
- Give cards a height or content with a height; the cards clip to a rounded surface, so do not add another card around the content.
- itemWidth is a fraction of the carousel width (0.84 by default), not points.
- With a controlled index, update it from onIndexChange, or autoplay and swipes cannot move it.
- CarouselParallax only works inside a Carousel card; put text after it so the text sits on top.

## Related

- [Segmented control](https://goodui.dev/native/components/segmented-control.md): Two to five views of the same thing with an elastic highlight that stretches toward the new segment, can be dragged, and ticks at every boundary. Includes a billing toggle.
- [Progress ring](https://goodui.dev/native/components/progress-ring.md): A circular progress ring that fills on a spring, laps itself past 100 % with a gap that lifts the lap off the ring, and bounces once with a success haptic when it completes.
- [Bottom sheet](https://goodui.dev/native/components/bottom-sheet.md): A sheet that rests at detents, follows the finger 1:1, settles with velocity and hands drags off to its scroll view.

## Guidance for AI tools

Carousel: A snapping card carousel that follows the finger 1:1, shrinks and dims the neighbours, stretches its pagination pill with the drag, and turns that pill into a timer when it autoplays. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
