A row of slides on a native scroll-snap track, with frosted prev and next buttons, dots and arrow keys.
Carousel lays its children out as slides on a scroll-snap track, so swipe, trackpad and the keyboard all scroll the same native element with no dependency. perView shows several at once on wide screens (one on phones); the buttons fade out at the ends unless loop is on.
autoplay (off by default) advances on a timer, stops while the pointer or focus is inside and has its own pause button, as WCAG 2.2.2 requires. Reduced motion scrolls instantly.
Installation
npx shadcn@latest add KezLahd/rime/carouselAlso installs: base, icon.
Usage
import { Carousel } from "@/components/ui";
<Carousel aria-label="Featured projects" perView={3}>
{projects.map((p) => <ProjectCard key={p.id} project={p} />)}
</Carousel>Examples
One per view
Three per view
API
Generated from the component's TypeScript types and JSDoc (scripts/gen-ui-registry.mjs), so it always matches the source.
Carousel
| Prop | Type | Default | Description |
|---|---|---|---|
| childrenRequired | ReactNode | One child per slide. | |
| aria-labelRequired | string | Names the carousel region: "Featured projects". | |
| perView | 1 | 2 | 3 | 4 | 1 | Slides visible at once on wide screens. Narrow screens show one. Default 1. |
| gap | number | 12 | Gap between slides in px. Default 12. |
| controls | boolean | true | Prev and next buttons. On by default. |
| dots | boolean | true | The dot row under the track. On by default. |
| autoplay | number | Advance every n ms. Off by default; pauses on hover and focus, and has a pause button. | |
| loop | boolean | false | Start again from the first slide after the last. Default false. |
| onIndexChange | (index: number) => void | ||
| className | string |
Theme tokens
Per-component tokens are undeclared by default, so the default shown is the fallback the CSS uses. Set one at :root (or in theme.css) to restyle every instance. Live values are read from this page, so they follow the Rime Studio.
| Token | Default | Live value |
|---|---|---|
| --carousel-gap | 12px | … |
| --carousel-nav-radius | var(--r-full) | … |
| --carousel-per-view | 1 | … |
| --carousel-radius | var(--r-lg) | … |
| --r-circle | 50% | … |
System tokens it reads
| Token | Default | Live value |
|---|---|---|
| --brand | tokens.css | … |
| --dur | tokens.css | … |
| --ease | tokens.css | … |
| --focus-ring | tokens.css | … |
| --ink-brand | tokens.css | … |
| --ink-heading | tokens.css | … |
| --ink-secondary | tokens.css | … |
| --line-input | tokens.css | … |
| --neutral-border | tokens.css | … |
| --popover-blur | tokens.css | … |
| --popover-surface | tokens.css | … |
| --r-full | tokens.css | … |
| --r-lg | tokens.css | … |
| --r-sm | tokens.css | … |
| --recess-fill | tokens.css | … |
| --recess-fill-hover | tokens.css | … |
| --shadow-float | tokens.css | … |
| --surface-solid | tokens.css | … |
| --text-caption | tokens.css | … |
Accessibility
- A section with aria-roledescription="carousel" and a required aria-label; each slide is a group with aria-roledescription="slide" labelled "2 of 5".
- Left and Right arrows move a slide; the track is focusable; off-screen slides are inert so Tab never lands on a hidden card.
- The track is a polite live region while not autoplaying, so a change of slide is announced.
Do and don't
Do
- Use it for browsable, optional content: featured items, screenshots.
- Show a partial next slide (perView 3 at narrower widths) so people see there is more.
Don't
- Don't put the only copy of important information on slide two or later.
- Don't turn autoplay on for content people need to read.
For AI agents
This page as markdown, rendered from the same registry entry. Point an agent at it (or add .md to this page's URL), or at /llms.txt for the index.
Open carousel.mdRelated: aspect-ratio, card