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