# Carousel

> Swipeable slide carousel on Embla. Previous/next buttons, dots, direction-aware keyboard arrows, optional autoplay that respects reduced motion. Everything mirrors in RTL.

Source: https://docs.nasaqui.com/components/carousel

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/carousel.json
```

A row of slides you swipe, drag or step through. Built on `embla-carousel-react`; the reading direction is passed to
Embla, so in Arabic the first slide is at the right and next moves left.

## When to use

- Image galleries, feature highlights, product rows that do not fit the width.

## When not to use

- Content the user must compare or all read: show it in a grid.
- Switching between views: use [`Tabs`](https://docs.nasaqui.com/components/tabs).

## Import

```tsx
import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, CarouselDots } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
<Carousel label="Gallery" className="max-w-xl">
  <CarouselContent>
    <CarouselItem>One</CarouselItem>
    <CarouselItem>Two</CarouselItem>
    <CarouselItem>Three</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
  <CarouselDots />
</Carousel>
```

## Anatomy

```
Carousel              role="region" aria-roledescription="carousel", sets dir and lang
├─ CarouselContent    viewport and track
│  └─ CarouselItem    role="group" aria-roledescription="slide" "Slide 2 of 5"
├─ CarouselPrevious   inline-start edge, chevron mirrors in RTL
├─ CarouselNext       inline-end edge
├─ CarouselDots       one button per snap, aria-current on the active one
└─ CarouselPlayPause  only when autoplay is on
```

## API

### `Carousel`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `loop?` | `boolean` | `false` | Wrap around at the ends. |
| `align?` | `"start" \| "center" \| "end"` | `"start"` | Where the active slide rests. |
| `opts?` | Embla options | none | Extra options. `direction` is always taken from the reading direction. |
| `autoplay?` | `boolean \| number` | `false` | Advance on a timer; a number is the interval in ms (5000 for `true`). |
| `setApi?` | `(api: CarouselApi) => void` | none | Receives the Embla API. |
| `label?` | `string` | "Carousel" | Accessible name. Localise it. |
| `locale?` / `dir?` | `string` / `"ltr" \| "rtl"` | from the provider | Override for isolated demos. |

`CarouselItem` is full width; set `basis-1/2` or `basis-1/3` to show several at once. `useCarouselContext()` returns
`{ api, selected, count, canPrev, canNext, rtl }` for custom controls.

## Accessibility

- The region is focusable; `Left` / `Right` move between slides and follow the reading direction (in RTL, `Left` goes to the next slide). Keys inside inputs are left alone.
- Each slide is announced as "Slide n of total"; the current position is in a polite live region unless autoplay is running.
- Autoplay is off under `prefers-reduced-motion`, pauses on hover, focus and hidden tabs, and stops for good after the user drags. `CarouselPlayPause` gives an explicit control.
- Dots and buttons have localised names and a 24px hit area.

## RTL & i18n

- Embla receives `direction: "rtl"`, so drag and slide order mirror.
- Previous/next sit on the start/end edges (`start-3`, `end-3`) and the chevrons use `Icon directional`.
- Default strings exist in English and Arabic.

## Styling & tokens

Slides use `ps-4` gutters (the track has `-ms-4`). Buttons use the `secondary` Button on `card` with `shadow-floating`. The dot for the current slide uses `primary`. No hex.

## Do / Don't

- **Do** give the carousel a `label` and every image real alt text.
- **Don't** hide essential content in slides after the first; users may never see them.
- **Don't** use autoplay for content that must be read.

## Related

- [Tabs](https://docs.nasaqui.com/components/tabs) · [Card](https://docs.nasaqui.com/components/card) · [Button](https://docs.nasaqui.com/components/button)

## Lab

https://docs.nasaqui.com/?path=/docs/components-data-display-carousel--docs

## Code

### React

```tsx
<Carousel label="Gallery" className="max-w-xl">
  <CarouselContent>
    <CarouselItem>One</CarouselItem>
    <CarouselItem>Two</CarouselItem>
    <CarouselItem>Three</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
  <CarouselDots />
</Carousel>
```

### shadcn

```tsx
<Carousel label="Gallery" className="max-w-xl">
  <CarouselContent>
    <CarouselItem>One</CarouselItem>
    <CarouselItem>Two</CarouselItem>
    <CarouselItem>Three</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
  <CarouselDots />
</Carousel>
```

### Vue

```vue
<script setup lang="ts">
import { NqCarousel, NqCarouselContent, NqCarouselDots, NqCarouselItem, NqCarouselNext, NqCarouselPrevious } from "@fadymondy/nasaq/vue";
</script>

<template>
  <NqCarousel label="Gallery" class="max-w-xl">
    <NqCarouselContent>
      <NqCarouselItem>One</NqCarouselItem>
      <NqCarouselItem>Two</NqCarouselItem>
      <NqCarouselItem>Three</NqCarouselItem>
    </NqCarouselContent>
    <NqCarouselPrevious />
    <NqCarouselNext />
    <NqCarouselDots />
  </NqCarousel>
</template>
```

### Blade

```blade
<x-nq::carousel label="Gallery" class="max-w-xl">
    <x-nq::carousel.content>
        <x-nq::carousel.item>One</x-nq::carousel.item>
        <x-nq::carousel.item>Two</x-nq::carousel.item>
        <x-nq::carousel.item>Three</x-nq::carousel.item>
    </x-nq::carousel.content>
    <x-nq::carousel.previous />
    <x-nq::carousel.next />
    <x-nq::carousel.dots />
</x-nq::carousel>
```

### HTML + Alpine

```html
<div role="region" aria-roledescription="carousel" aria-label="Gallery" dir="ltr" lang="en" tabindex="0"
    data-slot="carousel" x-data="nqCarousel(JSON.parse('{\u0022loop\u0022:false,\u0022align\u0022:\u0022start\u0022,\u0022autoplay\u0022:0,\u0022rtl\u0022:false,\u0022labels\u0022:{\u0022previous\u0022:\u0022Previous slide\u0022,\u0022next\u0022:\u0022Next slide\u0022,\u0022slides\u0022:\u0022Choose slide\u0022,\u0022goTo\u0022:\u0022Go to slide {n}\u0022,\u0022slide\u0022:\u0022Slide {n} of {total}\u0022,\u0022pause\u0022:\u0022Pause autoplay\u0022,\u0022play\u0022:\u0022Start autoplay\u0022}}'))" x-bind="root"
    class="relative rounded-card outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus max-w-xl">
    <div data-slot="carousel-viewport" class="overflow-x-auto overflow-y-hidden rounded-[inherit] snap-x snap-mandatory [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
    <div data-slot="carousel-content" class="-ms-4 flex touch-pan-y">
        <div role="group" aria-roledescription="slide" data-slot="carousel-item" class="min-w-0 shrink-0 grow-0 basis-full ps-4 snap-start -scroll-ms-4">One</div>
        <div role="group" aria-roledescription="slide" data-slot="carousel-item" class="min-w-0 shrink-0 grow-0 basis-full ps-4 snap-start -scroll-ms-4">Two</div>
        <div role="group" aria-roledescription="slide" data-slot="carousel-item" class="min-w-0 shrink-0 grow-0 basis-full ps-4 snap-start -scroll-ms-4">Three</div>
    </div>
</div>
    <button data-slot="carousel-previous"
     type="button"                  disabled      data-disabled     x-bind="previous" aria-label="Previous slide" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border text-foreground hover:bg-nq-hover size-control p-0 absolute top-1/2 z-10 -translate-y-1/2 rounded-full bg-card/90 shadow-floating backdrop-blur-sm data-disabled:opacity-0 start-3">
        <svg data-slot="icon" aria-hidden="true" class="rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m15 18-6-6 6-6"/>
</svg></button>
    <button data-slot="carousel-next"
     type="button"                         x-bind="next" aria-label="Next slide" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border text-foreground hover:bg-nq-hover size-control p-0 absolute top-1/2 z-10 -translate-y-1/2 rounded-full bg-card/90 shadow-floating backdrop-blur-sm data-disabled:opacity-0 end-3">
        <svg data-slot="icon" aria-hidden="true" class="rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m9 18 6-6-6-6"/>
</svg></button>
    <div role="group" aria-label="Choose slide" data-slot="carousel-dots" x-bind="dots" style="display: none"
    class="mt-3 flex items-center justify-center gap-1.5">
    <template x-for="i in count" :key="i">
        <button type="button" x-bind="dot(i - 1)" class="relative h-2 rounded-full outline-none transition-[width,background-color] duration-150 ease-nq focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus after:absolute after:-inset-2 after:content-['']"></button>
    </template>
</div>
    <div class="sr-only" aria-atomic="true" x-bind="live"></div>
</div>
```
