Display
Carousel
A row of slides the user swipes or steps through with Previous and Next buttons: recipe photos, a few featured cards. It is the browser's own scrolling with snap points, so swiping, trackpads and arrow keys all work and a vertical swipe still scrolls the page. Each child is a slide named "Slide 2 of 5", and moving to another slide announces that position politely, once. It never moves on its own, and with reduced motion the buttons jump instead of scrolling smoothly.
Install
Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):
npx shadcn@latest init --base base --preset nova --yes --no-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):
npx shadcn@latest add ourostack/ouro-ui/carousel#v1.2.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/carouselIts tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):
npx shadcn@latest add ourostack/ouro-ui/carousel#v1.2.13 ourostack/ouro-ui/carousel-tests#v1.2.13import { Carousel } from "@/components/ouro/carousel/carousel";Paths use shadcn's default @/ alias; the CLI rewrites them to yours.
Example
<Carousel label="Recipe photos">
{photos.map((photo) => <img key={photo.id} src={photo.src} alt={photo.alt} />)}
</Carousel>
<Carousel label="Featured recipes" slidesPerView={2} loop onIndexChange={setIndex}>
{recipes.map((recipe) => <RecipeCard key={recipe.id} recipe={recipe} />)}
</Carousel>Use instead when
- Use a grid instead when every item should be visible at once, and
Tabswhen the panels have names the user picks from.
Props
Type: CarouselProps.
children(required)- Type:
ReactNode - The slides: each child is one slide, so pass the images or cards themselves.
className- Type:
string - Layout only: width and margins (
max-w-xl,mx-auto). Appearance classes warn in development. defaultIndex- Type:
number - Default:
0 - The slide to start on, counting from 0. The starting slide is not announced.
label(required)- Type:
string - Names the carousel for screen readers, such as "Recipe photos". It is always required.
loop- Type:
boolean - Next on the last slide goes back to the first, and Previous on the first goes to the last.
nextLabel- Type:
string - Default:
"Next slide" - The Next button's accessible name.
onIndexChange- Type:
((index: number) => void) - Called with the index of the first visible slide when the user moves to another slide, once per change.
previousLabel- Type:
string - Default:
"Previous slide" - The Previous button's accessible name.
roleDescription- Type:
string - Default:
"carousel" - What a screen reader calls the carousel, after its label: "Recipe photos, carousel".
slideLabel- Type:
string - Default:
"Slide {n} of {total}" - Names each slide and is announced when the position changes:
{n}is the slide's number and{total}the number of slides. slideRoleDescription- Type:
string - Default:
"slide" - What a screen reader calls each slide, after its name: "Slide 2 of 5, slide".
slidesPerView- Type:
1 | 2 | 3 - Default:
1 - How many slides show at once.
Don't use
These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.
autoplay: Not a prop. Carousel never moves on its own: motion the user did not start is hard to read and to stop.opts: Not a prop. Ouro UI's Carousel is not Embla:<Carousel loop>.orientation: Not a prop. Carousel scrolls horizontally; stack vertical content in a grid instead.plugins: Not a prop. Carousel never moves on its own, so it takes no plugins.setApi: Not a prop. Read the position withonIndexChange:<Carousel onIndexChange={setIndex}>.
className is for layout only
Pass margins, width and grid placement (mt-4, w-full, col-span-2).
Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.
Alignment classes (text-center) warn too: set alignment on a parent element.