# Carousel

- Page: <https://kznlabs.github.io/labsUI/components/carousel>

## Carousel (`carousel`, component)

Native scroll-snap row, so touch, trackpad and no-JS scrolling work, with spring-driven arrows, page dots, arrow keys and mouse drag that throws to the next snap point. APG carousel semantics: a labeled region of slides announced as 3 of 8.

- Install: `npx @kznlabs/labsui add carousel`
- Docs: <https://kznlabs.github.io/labsUI/components/carousel>
- Brings: button, button-variants, utils
- npm: clsx@^2.1.1, lucide-react@1.63.0, motion@14.7.1, tailwind-merge@^3.7.0

### API

- `<Carousel>` (+ <section> props): Native scroll-snap row (touch, trackpad, no-JS all work) with spring-driven buttons, dots,
arrow keys and mouse drag. APG carousel semantics: a labeled region of "n of total" slides.
  - `label: string`: Accessible name of the carousel, e.g. "Projects".
- `<CarouselContent>` (+ <div> props): The scrolling row. Slides per view and the gap are CSS variables: `sm:[--per-view:2] [--gap:0.5rem]`.
- `<CarouselItem>` (+ <div> props): One slide, sized from the row's `--per-view` and `--gap` (or any `basis-*`). Announced as "3 of 8".
- `<CarouselPrevious>`: Overlaid at the left edge by default; hides at the start.
- `<CarouselNext>`: Overlaid at the right edge by default; hides at the end.
- `<CarouselDots>`: One dot per page (not per slide: with two slides in view there are fewer pages).

### Usage

```tsx
<Carousel label="Projects"><CarouselContent className="sm:[--per-view:2]"><CarouselItem>…</CarouselItem></CarouselContent><CarouselPrevious /><CarouselNext /><CarouselDots /></Carousel>
```

### Example: project-gallery.tsx

```tsx
import { AspectRatio } from "@/components/ui/aspect-ratio";
import {
  Carousel,
  CarouselContent,
  CarouselDots,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel";

type Project = { slug: string; title: string; cover: string };

// Slides scroll natively (touch, trackpad, no JS); buttons, dots and drag spring.
export function ProjectGallery({ projects }: { projects: Project[] }) {
  return (
    <Carousel label="Projects">
      <CarouselContent className="sm:[--per-view:2] lg:[--per-view:3]">
        {projects.map((p) => (
          <CarouselItem key={p.slug}>
            <AspectRatio ratio={16 / 9} className="rounded-xl">
              <img src={p.cover} alt={p.title} />
            </AspectRatio>
          </CarouselItem>
        ))}
      </CarouselContent>
      <CarouselPrevious />
      <CarouselNext />
      <CarouselDots />
    </Carousel>
  );
}
```
