Carousel
MD3 (Expressive) carousel: a horizontally scrolling row of 28dp-rounded
items. multi-browse and hero use Compose's keyline layout — items are
masked (never scaled) as they move between large, medium and small sizes.
Multi-browse
The default layout: one or more large items followed by medium and small
(40–56dp) items, snapping to the start. itemWidth is the preferred large
width; the actual size is fitted to the container.
<Carousel aria-label="Photos" itemWidth={260} itemHeight={200}>
{photos.map((photo) => (
<CarouselItem key={photo.id}>
<img src={photo.src} alt={photo.alt} />
</CarouselItem>
))}
</Carousel>
Images keep their size and are clipped by the item's mask.
Uncontained
Single-size items that flow past the edge of the container and scroll freely, without snapping.
<Carousel aria-label="Photos" variant="uncontained" itemWidth={200}>
…
</Carousel>
Hero
A large, centered item with a small item on each side, snapping to the center (the first and last items align to the start and end).
<Carousel aria-label="Featured" variant="hero" itemHeight={240}>
…
</Carousel>
Interactive items
onClick renders an item as a <button> and href as an <a>, with the
state layer, ripple and focus ring. Tab and Left / Right move between
items, and a focused item scrolls into the large position. disabled dims
an interactive item and removes it from the Tab order.
<Carousel aria-label="Albums">
{albums.map((album) => (
<CarouselItem key={album.id} aria-label={album.title} onClick={() => play(album)}>
<img src={album.cover} alt="" />
</CarouselItem>
))}
</Carousel>
Localized labels
Each item is announced with its position — "2 of 7" by default. Pass
getItemLabel to localize it, and roleDescriptionLabel /
itemRoleDescriptionLabel for the announced "carousel" / "slide" role
descriptions.
<Carousel
aria-label="Fotos"
getItemLabel={(position, count) => `${position} von ${count}`}
roleDescriptionLabel="Karussell"
itemRoleDescriptionLabel="Folie"
>
…
</Carousel>
Accessibility
- Always give the carousel an
aria-label: it is agroupannounced with the role description "carousel". - Non-interactive items are
groups announced as slides and named by their position ("2 of 7"); interactive items get their position as the accessible description, so name them with their content oraria-label. - With interactive items, Tab and Left / Right (mirrored in RTL) move between items. Without them, the scroll container itself is focusable so keyboard users can still scroll it.
- Under
prefers-reduced-motion: reduce, every item keeps one size (no keyline masking) and focus scrolling is instant. - Put nested interactive content only in non-interactive items.
Props
Carousel
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | — | Accessible name of the carousel. Required in practice — the container is a `group` announced as "carousel". |
children | ReactNode | — | `CarouselItem`s. |
getItemLabel | ((position: number, count: number) => string) | (position: number, count: number) => `${position} of ${count}` | Accessible label of each item's position, announced with the item (non-interactive items: the slide's name; `onClick` / `href` items: its description). `position` is 1-based. |
itemHeight | number | 200 | Item height in px. |
itemRoleDescriptionLabel | string | slide | Role description announced for each non-interactive item (`aria-roledescription`). |
itemWidth | number | 260 (`hero`: fills the container
beside its small items) | Preferred (large) item width in px. `multi-browse` / `hero` fit the actual large size to the container (Compose `preferredItemWidth`); `uncontained` uses it as-is. |
roleDescriptionLabel | string | carousel | Role description announced for the carousel container (`aria-roledescription`). |
spacing | number | 8 | Gap between items in px. |
variant | enum | multi-browse | Layout (m3.material.io / Compose `HorizontalMultiBrowseCarousel`, `HorizontalUncontainedCarousel`, `HorizontalCenteredHeroCarousel`): - `multi-browse` — large, medium and small (40–56dp) items, start-snapped; - `uncontained` — single-size items flowing past the edge, free scrolling; - `hero` — centered hero: a large item with a small item on each side, center-snapped (the first / last item align to the start / end). |
CarouselItem
| Prop | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disable an interactive (`onClick` / `href`) item: dimmed, not focusable. |
download | any | — | Download hint (with `href`). |
href | string | — | Makes the item a link (`<a>`): activated with Enter, opens in new tabs. |
rel | string | — | Link relationship (with `href`). |
target | HTMLAttributeAnchorTarget | — | Link target (with `href`). |