/// Media
Carousel
Accessible slide carousel — a scroll-snap track with mono prev/next buttons, square dot indicators, arrow-key navigation and optional autoplay.
import { Carousel } from "@protocore/pds";Basics
Wrap each panel in a Carousel.Slide. The region gets aria-roledescription="carousel"; prev/next buttons and a dot per slide are drawn for you. Give the region a name via aria-label.
Looping autoplay
Set autoPlay with loop to rotate continuously. Autoplay pauses while the pointer is over the carousel or focus is inside it, and is disabled entirely under prefers-reduced-motion.
Media gallery
Slides can hold anything — pair with AspectRatio to keep image panels a fixed shape as the track resizes.
When to use it
Use Carousel for a small, browsable set of peer panels where showing one at a time is intentional — a hero rotation, a testimonial reel, a screenshot gallery. It degrades gracefully: the track is a native CSS scroll-snap container, so slides remain swipeable even if JavaScript never loads.
- Carrying navigation between views of one entity? That's Tabs, not a carousel.
- Showing a continuous logo strip? Use LogoMarquee.
- Hiding essential content behind autoplay is an anti-pattern — keep autoplay for ambient, non-critical panels and never bury a primary CTA on slide 3.
Usage
Do
- Give the carousel an accessible name with `aria-label`.
- Keep autoplay for ambient content; it already pauses on hover/focus and respects reduced-motion.
- Use `loop` when the set is a true rotation with no natural start or end.
- Keep each slide's content self-contained so any slide reads on its own.
Don't
- Don't autoplay content users must read or act on — they can't keep pace.
- Don't put your only copy of a primary action inside a non-first slide.
- Don't use a carousel where a simple grid or stack would show everything at once.
- Don't remove the dots and arrows — they're the visible, keyboard-reachable controls.
Accessibility
| Keys | Action |
|---|---|
| Arrow Left / Right | Move to the previous / next slide. |
| Home / End | Jump to the first / last slide. |
| Tab | Move into the prev/next buttons and the dot controls. |
| Enter / Space | Activate the focused control. |
- The region carries `aria-roledescription="carousel"`; each slide is a `role="group"` labelled "n of m" (override per slide with your own `aria-label`).
- The slide track is an `aria-live` region — polite while paused, off while autoplaying — so slide changes are announced without fighting a running rotation.
- Prev/next arrows are inline SVGs (never emoji glyphs); the active dot uses the reserved accent as the current-slide marker.
- Autoplay honours `prefers-reduced-motion` (no rotation) and pauses on hover and on focus within the carousel.
Carousel props
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | Carousel | Accessible name for the carousel region. Default `"Carousel"`. |
autoPlay | boolean | false | Advance automatically. Paused on hover/focus and disabled under reduced-motion. Default `false`. |
children * | ReactNode | — | `Carousel.Slide` children. |
className | string | — | |
defaultIndex | number | 0 | Initial slide index in uncontrolled mode. Default `0`. |
hideControls | boolean | false | Hide the prev/next/dots control row (e.g. for a swipe-only mobile track). Default `false`. |
index | number | — | Active slide index (controlled). |
interval | number | 5000 | Autoplay dwell time per slide, in ms. Default `5000`. |
loop | boolean | false | Wrap from last→first (and first→last) instead of stopping at the ends. Default `false`. |
onIndexChange | ((index: number) => void) | — | Fires whenever the active slide changes (both modes). |
style | CSSProperties | — |