Skip to content

Carousel

<Carousel> wraps Embla with the design system’s controls. Use sparingly — the eye scans grids better than carousels for most data.

import from "@na/ui/components/carousel" ▶ Open in Storybook packages/ui/src/components/carousel.tsx

When to use

  • Onboarding hero slides where horizontal motion adds context.
  • Image-rich entity galleries (rare in admin; common in e-commerce).
  • Constrained-width areas where vertical scroll is already in use.

Don’t use Carousel for: lists with exact-match retrieval (use a table or grid + filters), settings (auto-rotation hurts clarity), or “showcase” carousels users have to wait through. Auto-rotate is forbidden by default.

One slide visible

Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
<Carousel>
<CarouselContent>
{items.map((it, i) => (
<CarouselItem key={i}>{/* slide */}</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>

Multiple slides visible

Set opts={{ align: 'start' }} and size each item with Tailwind basis (basis-1/3).

1
2
3
4
5
6
7
<Carousel opts={{ align: 'start' }}>
<CarouselContent>
{items.map((it, i) => (
<CarouselItem key={i} className="basis-1/3">{/* slide */}</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>

API

Prop Type Default Description
orientation "horizontal" | "vertical" "horizontal" Scroll direction.
opts EmblaOptionsType Forwarded to Embla. Common: align, loop, slidesToScroll.
plugins EmblaPluginType[] Embla plugins (autoplay, fade, etc.).
setApi (api: EmblaCarouselType) => void Receive the Embla instance for advanced control.

CarouselContent / CarouselItem / CarouselPrevious / CarouselNext accept native attributes; the prev / next are styled <Button> instances.

Design guidelines

✓ Do

  • Use Carousel only when horizontal motion adds something — e.g., constrained vertical space.
  • Show prev / next controls. Don't rely on swipe alone — desktop has no swipe.
  • Pair with a slide indicator (dots) when there are 5+ slides.

✗ Don't

  • Auto-rotate. The user is not a billboard. If you must, never on a settings or content page.
  • Use Carousel for lists the user could pick from — that's a list / table / grid.
  • Loop infinitely without a clear "first" anchor. Users get disoriented.

Accessibility

  • Embla provides role="region" aria-roledescription="carousel"; items are role="group" aria-roledescription="slide".
  • Prev / Next are real <Button> instances. <Tab> reaches them; Enter activates.
  • Auto-rotation must be paused on focus / hover (Embla autoplay plugin handles this; verify before shipping).
  • Card + CardGrid — Default for “show a set of things.”

▶ Open Carousel stories in Storybook