Skip to content

ExpressiveCSS

Browse documentation

Carousel

Material 3 adaptive carousels for visual collections.

View as Markdown

Add .carousel to show a scrollable collection of visual items. The default is Material 3's multi-browse layout, where 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.

<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>

Text sits on an opaque bounding shape in surface and on-surface colors, 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 shows one large visual. full-screen is an immersive vertical feed.

Uncontained

Uncontained items keep one width and can stop anywhere. Add snap if you want them to snap. Add multi-aspect only when the source media has different aspect ratios. Widths stay between 9:16 and 16:9.

<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 to 56dp preview of the next item. Add center-aligned to center the large item and preview both neighbors. Hero always snap-scrolls.

<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 becomes 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. Buttons and links inside items keep ordinary click activation, and dragging does not activate them.
  • Focus starts on the first item, never on the container.
  • Left and right arrows follow the horizontal reading direction, including RTL. 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.

Show all is also the single-pointer path through the items (WCAG 2.5.7). Dragging the track is the only other way a mouse alone can move it.

Mountain lake Forest path Rocky coastline Beach at dusk
<div class="carousel" aria-label="Recent trips">
  <article class="carousel-item"><img src="lake.jpg" alt="Mountain lake" /></article>
  <article class="carousel-item"><img src="forest.jpg" alt="Forest path" /></article>
</div>
<button type="button" onclick="document.getElementById('all-trips').showModal()">Show all</button>
<dialog id="all-trips" class="max" aria-labelledby="all-trips-title">
  <header>
    <button type="button" aria-label="Close" onclick="this.closest('dialog').close()">
      <span class="material-symbols" aria-hidden="true">close</span>
    </button>
    <h2 id="all-trips-title">Recent trips</h2>
  </header>
  <div>
    <img src="lake.jpg" alt="Mountain lake" class="responsive-img" />
    <img src="forest.jpg" alt="Forest path" class="responsive-img" />
  </div>
</dialog>

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 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.

Set an interval to make the carousel advance on its own. Content that moves by itself has to be stoppable, so no option turns off the pause contract. An interval always pauses while the pointer is over the carousel, while focus is inside it, and while the tab is in the background. 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. Auto-advance is the only caller that can loop back past them.

The carousel responds to reduced-motion changes while mounted, and the change does not clear an explicit pause. Editable descendants keep their own navigation keys. Testing with real screen readers, native zoom and physical devices is still pending.

Application-owned pause control

If you turn on automatic movement, add a visible pause and resume button that calls pause() and start(). Resuming still respects hover, focus, background-tab and reduced-motion suspension.

const carousel = Expressive.Carousel.init(
  document.querySelector('#carousel-motion'),
  { interval: 4000, height: 200 }
);
const toggle = document.querySelector('#carousel-motion-toggle');
let paused = false;
toggle.addEventListener('click', () => {
  paused = !paused;
  if (paused) carousel.pause();
  else carousel.start();
  toggle.textContent = paused ? 'Resume automatic movement' : 'Pause automatic movement';
});

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 carousel still reads the older --carousel-height author hook. The compiled component source also has tokens for the interactive state layer, focus indicator, and disabled opacity.

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.
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 of the track either way.
interval Number 0 Milliseconds to rest between automatic advances, on top of duration. The rest starts after each transition ends, so a full cycle takes duration + interval. 0 leaves auto-advance off, and setting it turns on the pause contract described under Behavior and accessibility. Each rest starts from the move before it, not from a fixed schedule, so a dropped tick costs a whole rest instead of the remainder 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.

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow