Carousel
Material Design 3 Expressive Carousel displays a dynamic, scrollable list of items with keyline matrix masking, parallax movement, and hybrid Show All controls.
Material Design 3 Expressive Carousels display a scrollable sequence of visual items. They dynamically interpolate item sizes across keyline matrix boundaries as content scrolls into and out of view.
Introduction
Ported directly from Jetpack Compose Material 3 Carousel specification, the Carousel component for React 19 and Next.js 19 (App Router) provides fluid keyline masking, dynamic parallax offsets, and dual-render support for both React Server Components (RSC) and Client-Side Rendering (CSR).
Anatomy
- Container (
role="region"): The primary horizontal or vertical scroll viewport (aria-roledescription="carousel"). - Carousel Items (
role="group"): Individual slide wrappers (aria-roledescription="slide") with expressive28pxcorner radius shape. - Keyline Mask Layer: Dynamic clip-path inset mask that smoothly expands and collapses items as they move across container edges.
- Parallax Content Container: Inner content container translating relative to scroll position to create visual depth.
- Show All Action Button: Compliant 48px touch-target button that toggles between horizontal carousel scrolling and vertical responsive grid layout.
Layout Variants
Multi-Browse Layout
Displays a mixture of Large, Medium, and Small items simultaneously. Smoothly interpolates item sizes during horizontal scrolling. Recommended for browsing photo galleries or visual content feeds.
Uncontained Layouts
Items maintain a fixed width or variable aspect ratio (16:9, 4:3, 1:1, 9:16) and bleed beyond the edge of the container. Supports default free scrolling.
Hero Layouts
Spotlights one primary Large item alongside Small preview items. Features mandatory single-item advance snap scrolling. center-aligned-hero centers the Large item with Small previews on both leading and trailing edges.
Full-Screen Vertical Layout
Edge-to-edge vertical scrolling carousel with mandatory snap-scrolling. Ideal for full-bleed video feeds or immersive story presentations.
Hybrid "Show All" Controls
Supports both Uncontrolled mode (internally toggling to a responsive display: grid with auto-fill columns) and Controlled mode (onShowAllClick callback delegating routing to Next.js Intercepting Routes).
Features
Dynamic Masking & Keyline Matrix
Calculates keylines dynamically based on container viewport width and preferredItemWidth. As items scroll across keyline boundaries, clip-path masks (clip-path: inset(...)) execute smooth expanding and collapsing transitions between Large, Medium, and Small items.
Parallax Movement
Applies a subtle inner content translation (transform: translateX(...)) relative to scroll position to mimic Jetpack Compose parallax scroll physics.
RSC & CSR Dual-Render API
- RSC / Next.js Image Mode (
children): Pass direct React Nodes or Next.js<Image />components into<Carousel>without client serialization errors. - CSR / Data-driven Mode (
items + renderItem): Pass an array of data items and a render callback.
Reduced Motion Handling
Automatically respects system @media (prefers-reduced-motion: reduce) settings. When enabled, inner parallax movements and dynamic keyline masks are disabled.
Usage
import { Carousel, Card } from "@bug-on/m3-expressive";
// 1. RSC Mode (Direct Children)
<Carousel layout="multi-browse" preferredItemWidth={300} aria-label="Destinations">
<Card variant="filled">
<img src="/photo1.jpg" alt="Mountain" />
<h4>Mountain Retreat</h4>
</Card>
<Card variant="filled">
<img src="/photo2.jpg" alt="Ocean" />
<h4>Ocean Breeze</h4>
</Card>
</Carousel>
// 2. CSR Data-driven Mode
<Carousel
layout="hero"
items={myItemsArray}
renderItem={(item, index) => (
<Card variant="elevated">
<h4>{item.title}</h4>
<p>{item.description}</p>
</Card>
)}
/>
// 3. Controlled "Show All" Route Delegation
<Carousel
layout="multi-browse"
onShowAllClick={() => router.push('/gallery/all')}
showAllLabel="View All"
>
{/* Slides */}
</Carousel>
Best Practices
Do
- Set the
preferredItemWidthappropriately so image thumbnails and text remain legible. - Include an accessible
aria-labelor header title describing the carousel content. - Provide a "Show All" button on vertically scrolling pages for accessibility compliance.
- Use
herolayout when spotlighting featured single items.
Don't
- Don't shrink small item widths below
40px(min range: 40px to 56px). - Don't place floating arrow buttons inside or on top of carousel content items.
- Don't override keyline scroll snapping with legacy fixed pixel offsets.
Design Tokens
Dimensional Tokens
| Token | Value | Description |
|---|---|---|
--carousel-item-shape | 28px | M3 extraLarge shape corner radius |
--carousel-item-spacing | 8px | Default gap between adjacent carousel items |
--carousel-content-padding | 16px | Leading and trailing container padding |
--carousel-vertical-padding | 8px | Top and bottom container padding |
--carousel-small-item-min | 40px | Minimum width limit for small preview items |
--carousel-small-item-max | 56px | Maximum width limit for small preview items |
Interactive Overlay Opacities
| State Layer | Opacity Token | Color Token |
|---|---|---|
| Hover Layer | 0.08 | var(--md-sys-color-on-surface) |
| Focus Layer | 0.1 | var(--md-sys-color-on-surface) |
| Pressed Layer | 0.1 | var(--md-sys-color-on-surface) |
| Focus Indicator | 3px thickness, 2px offset | var(--md-sys-color-secondary) |
Accessibility
- Container Role: Renders
role="region"witharia-roledescription="carousel"and accessible label. - Item Roles: Renders
role="group"witharia-roledescription="slide"andaria-label="Item X of Y". - Keyboard Support:
Tab: Moves focus into the active carousel item (index 0).ArrowLeft/ArrowRight: Navigates to previous or next slide and scrolls it into view.ArrowUp/ArrowDown: Exits carousel navigation to move focus out to adjacent page controls.Space/Enter: Activates the currently focused carousel slide.
- Show All Target: Minimum touch target size of 48×48px.
API Reference
Carousel
| Prop | Type | Default | Description |
|---|---|---|---|
layout | CarouselLayout | "multi-browse" | M3 layout variant (multi-browse, uncontained, uncontained-multi-aspect, hero, center-aligned-hero, full-screen). |
children | ReactNode | — | RSC Mode: Direct React nodes or server components. |
items | T[] | — | CSR Mode: Data array. |
renderItem | (item: T, index: number) => ReactNode | — | CSR Mode: Render callback for item rendering. |
preferredItemWidth | number | 300 | Preferred width for large items in px. |
itemSpacing | number | 8 | Gap between adjacent items in px. |
contentPadding | number | 16 | Leading/trailing container padding in px. |
verticalPadding | number | 8 | Top/bottom container padding in px. |
isExpanded | boolean | — | Controlled state for expanded Grid view. |
onShowAllClick | () => void | — | Callback fired when 'Show all' is clicked. |
showAllLabel | string | "Show all" | Label text for 'Show all' button. |
userScrollEnabled | boolean | true | Enable or disable user gestures/scrolling. |
aria-label | string | "Content carousel" | Accessible title/label for carousel container. |