MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

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 expressive 28px corner 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.

Loading demo...

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.

Loading demo...

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.

Loading demo...

Full-Screen Vertical Layout

Edge-to-edge vertical scrolling carousel with mandatory snap-scrolling. Ideal for full-bleed video feeds or immersive story presentations.

Loading demo...

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

Loading demo...

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 preferredItemWidth appropriately so image thumbnails and text remain legible.
  • Include an accessible aria-label or header title describing the carousel content.
  • Provide a "Show All" button on vertically scrolling pages for accessibility compliance.
  • Use hero layout 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

TokenValueDescription
--carousel-item-shape28pxM3 extraLarge shape corner radius
--carousel-item-spacing8pxDefault gap between adjacent carousel items
--carousel-content-padding16pxLeading and trailing container padding
--carousel-vertical-padding8pxTop and bottom container padding
--carousel-small-item-min40pxMinimum width limit for small preview items
--carousel-small-item-max56pxMaximum width limit for small preview items

Interactive Overlay Opacities

State LayerOpacity TokenColor Token
Hover Layer0.08var(--md-sys-color-on-surface)
Focus Layer0.1var(--md-sys-color-on-surface)
Pressed Layer0.1var(--md-sys-color-on-surface)
Focus Indicator3px thickness, 2px offsetvar(--md-sys-color-secondary)

Accessibility

  • Container Role: Renders role="region" with aria-roledescription="carousel" and accessible label.
  • Item Roles: Renders role="group" with aria-roledescription="slide" and aria-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

PropTypeDefaultDescription
layoutCarouselLayout"multi-browse"M3 layout variant (multi-browse, uncontained, uncontained-multi-aspect, hero, center-aligned-hero, full-screen).
childrenReactNodeRSC Mode: Direct React nodes or server components.
itemsT[]CSR Mode: Data array.
renderItem(item: T, index: number) => ReactNodeCSR Mode: Render callback for item rendering.
preferredItemWidthnumber300Preferred width for large items in px.
itemSpacingnumber8Gap between adjacent items in px.
contentPaddingnumber16Leading/trailing container padding in px.
verticalPaddingnumber8Top/bottom container padding in px.
isExpandedbooleanControlled state for expanded Grid view.
onShowAllClick() => voidCallback fired when 'Show all' is clicked.
showAllLabelstring"Show all"Label text for 'Show all' button.
userScrollEnabledbooleantrueEnable or disable user gestures/scrolling.
aria-labelstring"Content carousel"Accessible title/label for carousel container.