# Carousel

> Material 3 adaptive carousels for visual collections.

## Introduction

A `.carousel` displays a scrollable collection of visual items. The default is Material 3's multi-browse layout: one large, one medium, and one small item adapt as the active item changes. `AutoInit()` starts every carousel except `no-autoinit`.

Give the container an accessible name and every direct item the `carousel-item` class. Keep item text brief. At compact widths, use no more than two lines of text and no more than three text-heavy items on screen.

 [![Mountain lake](https://picsum.photos/id/1015/800/500) Mountain lake](#mountain-lake)[![Forest path](https://picsum.photos/id/1016/800/500) Forest path ](#forest-path)[![Rocky coastline](https://picsum.photos/id/1018/800/500) Rocky coastline ](#rocky-coastline)[![Clouds over hills](https://picsum.photos/id/1019/800/500) Cloudy hills ](#cloudy-hills)[![A dog in the grass](https://picsum.photos/id/1025/800/500) Dog in the grass](#dog-in-grass)

[Show all](#all-items)

```
<div class="carousel" aria-label="Featured landscapes">
  <a class="carousel-item" href="mountain-lake.html">
    <img src="images/mountain-lake.jpg" alt="Mountain lake" />
    <span class="carousel-item-content">Mountain lake</span>
  </a>
  <a class="carousel-item" href="forest-path.html">
    <img src="images/forest-path.jpg" alt="Forest path" />
    <span class="carousel-item-content">Forest path</span>
  </a>
  <a class="carousel-item" href="rocky-coastline.html">
    <img src="images/rocky-coastline.jpg" alt="Rocky coastline" />
    <span class="carousel-item-content">Rocky coastline</span>
  </a>
</div>
<div class="mt-1">
  <a class="button text" href="all-landscapes.html">Show all</a>
</div>
```

The text treatment is an opaque `surface` / `on-surface` bounding shape, so its contrast does not depend on the image. The component uses native scrolling, 8dp gaps, 16dp inline padding, 8dp block padding, and 28dp item corners.

## Layouts

Choose one layout for the content. Multi-browse is the default and is best for many simple visuals. `uncontained` suits text-heavy or highly customized items. `hero` spotlights one visual. `full-screen` is an immersive vertical feed.

### Uncontained

Uncontained items keep one width and can stop anywhere. Add `snap` when snapping is preferable. Add `multi-aspect` only when the source media genuinely has different aspect ratios; widths stay between 9:16 and 16:9.

[![Mountain lake](https://picsum.photos/id/1015/800/500)](#lake-story) [![Forest path](https://picsum.photos/id/1016/800/500)](#forest-story) [![Rocky coastline](https://picsum.photos/id/1018/800/500)](#coast-story)

```
<div class="carousel uncontained" aria-label="Travel stories">
  <a class="carousel-item" href="lake.html"
    ><img src="lake.jpg" alt="Mountain lake"
  /></a>
  <a class="carousel-item" href="forest.html"
    ><img src="forest.jpg" alt="Forest path"
  /></a>
</div>

<div class="carousel uncontained multi-aspect" aria-label="Mixed-format photos">
  <a
    class="carousel-item"
    style="--md-comp-carousel-item-aspect-ratio: 9 / 16"
    href="portrait.html"
    >…</a
  >
  <a
    class="carousel-item"
    style="--md-comp-carousel-item-aspect-ratio: 1 / 1"
    href="square.html"
    >…</a
  >
  <a
    class="carousel-item"
    style="--md-comp-carousel-item-aspect-ratio: 16 / 9"
    href="landscape.html"
    >…</a
  >
</div>
```

### Hero

Hero shows one large item and a 40–56dp preview of the next item. Add `center-aligned` to center the large item and preview both neighbors. Hero always snap-scrolls.

[![Mountain lake](https://picsum.photos/id/1015/1000/600)](#destination-one) [![Forest path](https://picsum.photos/id/1016/1000/600)](#destination-two) [![Rocky coastline](https://picsum.photos/id/1018/1000/600)](#destination-three)

```
<div class="carousel hero" aria-label="Featured destinations">…</div>
<div class="carousel hero center-aligned" aria-label="Featured destinations">
  …
</div>
```

### Full-screen

Full-screen is vertical, edge-to-edge, and always snap-scrolls one item at a time in portrait compact and medium layouts. In landscape or at expanded widths, the same markup automatically adapts to a horizontal hero.

```
<div class="carousel full-screen" aria-label="Featured stories">
  <article class="carousel-item" tabindex="0">…</article>
  <article class="carousel-item" tabindex="0">…</article>
</div>
```

## Behavior and accessibility

-   Multi-browse, hero, and full-screen snap to their layouts.
-   Uncontained uses free scrolling unless `snap` is added.
-   Images receive a subtle parallax offset while scrolling.
-   At medium, large, and extra-large container widths, two, three, or four large items fit before the medium and small items.
-   Mouse users can drag the track with a grab cursor. Trackpads keep native two-axis page and carousel scrolling.
-   Focus starts on the first item, never on the container.
-   Left and right arrows move through horizontal items. Up and down arrows move through a full-screen carousel and otherwise leave the carousel.
-   Home and End move directly to the first and last items.

On a vertically scrolling page, place a **Show all** action 4dp below every horizontal carousel. It should open a normal vertically scrolling view of the same items. If the carousel has a heading, a 48dp arrow action may sit beside that heading instead. Do not overlay previous/next controls on the carousel or place them beside its edges.

The component supplies a `region`, carousel and slide role descriptions, item position labels, and indicator labels when authors do not provide them. Localize generated strings with the `i18n` option. Layout changes follow the carousel's rendered width, including when a pane or side navigation resizes without a window resize. A full-screen carousel automatically becomes a horizontal hero in landscape or at an expanded width. Reduced-motion mode removes parallax and size morphing, uses equal item widths, and disables smooth scrolling.

An `interval` makes the carousel advance on its own, and content that moves by itself has to be stoppable. So the pause contract is not optional and there is no option that turns it off: an interval always pauses while the pointer is over the carousel, while focus is inside it, and while the tab is in the background, and `prefers-reduced-motion: reduce` suppresses auto-advance altogether. An explicit `noWrap` stops it after one pass instead of looping. Arrow keys and `set()` stop at the ends either way — a scroll track has ends, and auto-advance is the one caller that can loop back past them.

## Tokens

| Token | Default |
| --- | --- |
| `--md-comp-carousel-height` | 240px compact; 320px medium; 360px expanded |
| `--md-comp-carousel-shape` | 28px |
| `--md-comp-carousel-pressed-shape` | 20px |
| `--md-comp-carousel-gap` | 8px |
| `--md-comp-carousel-inline-padding` | 16px |
| `--md-comp-carousel-block-padding` | 8px |
| `--md-comp-carousel-large-item-width` | Responsive, capped by layout |
| `--md-comp-carousel-medium-item-width` | Responsive |
| `--md-comp-carousel-small-item-min-width` | 40px |
| `--md-comp-carousel-small-item-max-width` | 56px |
| `--md-comp-carousel-uncontained-item-width` | min(78%, 320px) |
| `--md-comp-carousel-item-aspect-ratio` | Per-item, multi-aspect only |
| `--md-comp-carousel-indicator-allowance` | 40px, fixed height only |

The older `--carousel-height` author hook is still read. Interactive state layer, focus indicator, and disabled opacity tokens are also available in the compiled component source.

## Initialization

```
document.addEventListener('DOMContentLoaded', function() {
  const elems = document.querySelectorAll('.carousel');
  const instances = Expressive.Carousel.init(elems);
});
```

```
Expressive.AutoInit(document.body, {
  Carousel: {
    i18n: { carousel: 'Galería', item: 'Elemento', of: 'de' }
  }
});
```

## Options

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `duration` | Number | `200` | Milliseconds allowed for a programmatic scroll to land, and the part of each auto-advance rest that is not `interval`. |
| `fullWidth` | Boolean | `false` | Add the full-width compatibility layout used by swipeable tabs. Prefer an M3 layout class for new carousels. |
| `indicators` | Boolean | `false` | Show legacy paging dots. M3 recommends a nearby Show all path instead of overlay controls. |
| `noWrap` | Boolean | `false` | Stop auto-advance at the last item instead of looping back to the first. Arrow keys and `set()` stop at the ends either way — a scroll track has ends. |
| `interval` | Number | `0` | Milliseconds to rest between automatic advances, on top of `duration` — the gap follows each transition rather than containing it, so a full cycle takes `duration + interval`. `0` leaves auto-advance off, and setting it turns on the pause contract below. Each rest is armed by the move before it rather than on a fixed phase, so a dropped tick costs a whole rest rather than leaving whatever is left of one. |
| `height` | Number | `null` | Fixed track height in pixels. `null` sizes the carousel from its content. A fixed height gives the indicators their own row below the track instead of laying them over the media. |
| `onCycleTo` | Function | `null` | Called when the active item changes. Receives the item and whether the move was a drag. |
| `i18n` | Object | `{ carousel: 'Carousel', item: 'Item', of: 'of', indicators: 'Slides', slide: 'Slide' }` | Generated accessible label strings. `indicators` names the indicator row and `slide` prefixes each dot, giving “Slide 1”. Partial objects merge with the defaults. |

## Methods

```
const instance = Expressive.Carousel.getInstance(elem);

instance.next();
instance.prev();
instance.set(3); // zero-based item index
instance.pause(); // stop auto-advance
instance.start(); // resume it
instance.destroy();
```

`next` and `prev` accept an optional item count. `set` accepts an optional one-shot callback. `pause` and `start` stop and resume auto-advance, and do nothing without an `interval`. `destroy` removes generated labels, size roles, indicators, listeners, the scroll-track wrapper, and the auto-advance timer.
