# Carousel

The slides, one at a time or a few, that a person moves through with buttons, arrow keys or by swiping. It never moves by itself unless you set an `interval`. Under reduced motion or `data-motion="calm"` the slides jump instead of gliding and the interval stops; `data-motion="none"` does the same.

- Registry name: `carousel` (`@candor/carousel`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/carousel

## Install

```bash
npx candor-ui add carousel
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/carousel`.
It brings in: button, locale, motion, tokens, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, CarouselStatus } from "@/components/ui/carousel"

const reports = [
  { title: "Weekly deploys", body: "31 deploys, 2 rolled back." },
  { title: "Open incidents", body: "1 open, 4 resolved this week." },
  { title: "Build time", body: "Median 6 min 12 s over the last 100 builds." },
  { title: "Test flakes", body: "3 tests failed and passed on retry." },
]

// It never moves by itself: add `interval` to auto-advance, and it still stops for the pointer, focus and reduced motion.
export default function Example() {
  return (
    <Carousel label="Weekly reports" className="max-w-md">
      <CarouselContent>
        {reports.map((r) => (
          <CarouselItem key={r.title}>
            <div className="flex h-32 flex-col justify-center gap-1 rounded-md border bg-card px-16">
              <h3 className="text-sm font-medium">{r.title}</h3>
              <p className="text-sm text-muted-foreground">{r.body}</p>
            </div>
          </CarouselItem>
        ))}
      </CarouselContent>
      <CarouselPrevious />
      <CarouselNext />
      <div className="mt-2 text-center">
        <CarouselStatus />
      </div>
    </Carousel>
  )
}
```

## <Carousel>

Props:

- `interval?: number | undefined`: Milliseconds between automatic advances. Left out, the carousel never moves by itself. When set it still stays still while the pointer is over it or focus is inside it, after the person pauses it, and under reduced motion, `data-motion="calm"` or `data-motion="none"`, and it shows a pause button.
- `onIndexChange?: ((index: number) => void) | undefined`: Called with the first visible slide's position (from 0) when it changes, by the buttons, keys, swiping or the interval.
- `label?: string | undefined`: The carousel's accessible name. Defaults to "Carousel".
- `className?: string | undefined`
- `children?: React.ReactNode | undefined`: `CarouselContent` with the slides, and the previous, next and status controls.

## <CarouselContent>

The scrolling row of slides; its direct children are the `CarouselItem`s. It scrolls and snaps natively, so a finger or trackpad moves it exactly as far as the pointer goes. It is a tab stop: Left and Right (mirrored in right-to-left languages) change slide, Home and End go to the ends.

No props.

Also takes the props of `React.ComponentProps<"div">`.

## <CarouselItem>

One slide, labelled "3 of 8" for assistive technology. Give it a width with `basis-*` to show more than one at a time.

No props.

Also takes the props of `React.ComponentProps<"div">`.

## <CarouselPrevious>

Goes to the previous slide. At the first slide it stays focusable, is `aria-disabled` and says so.

Props:

- `className?: string | undefined`

## <CarouselNext>

Goes to the next slide. At the last slide it stays focusable, is `aria-disabled` and says so.

Props:

- `className?: string | undefined`

## <CarouselStatus>

"3 of 8": the first visible slide out of all of them, in the locale's digits. Announced politely when it changes.

Props:

- `className?: string | undefined`

## Strings (`carousel`)

The fixed words, English by default. Override any of them under `carousel` in `<LocaleProvider strings={{ carousel: { … } }}>`; the table is exported as `carouselStrings`.

- `carousel`: Carousel
- `slide`: Slide
- `slideOf`: {current} of {total}
- `previous`: Previous slide
- `next`: Next slide
- `atStart`: This is the first slide.
- `atEnd`: This is the last slide.
- `pause`: Pause automatic advance
- `resume`: Resume automatic advance

## Motion

- **slide** (move): The slides glide to the one the person chose with the buttons or arrow keys, or to the next one when the interval fires. Swiping moves the row exactly as far as the finger goes, with nothing added. Under reduced motion the slides jump.

Set with `--cd-motion-carousel-<moment>-*` in your brand file; the families are in the design reference.

## Terms used here

- **Calm level**: What Candor's motion does for a person whose operating system asks for reduced motion: the highest-precedence setting, above the app's own motion level (`none`, `calm`, `subtle` or `expressive`) and the brand file. A theme tunes how a change looks, never when it happens, so nothing depends on seeing the motion. (https://candor.themethodic.com/glossary.md#calm-level)

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
