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. Items interpolate through the same keyline matrix at both edges of the viewport and snap to the nearest valid arrangement when released. 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...

Multi-Aspect Ratio Layout

Maintains standard Material Design 3 and Figma aspect ratio proportions (16:9, 4:3, 1:1, 3:4, 9:16) across carousel items.

Loading demo...

Designed for Android XR and spatial computing environments. Features an elevated glassmorphic panel (backdrop-blur-md, subtle border, spatial shadow) and floating orbiter navigation controls (showControls="orbiters").

Loading demo...

Hybrid "Show All" Controls

Supports both Uncontrolled mode (internally toggling to a responsive display: grid with auto-fill columns when showAllButton is true) 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. A cached geometry model drives each animation frame, so the scrolling position, item mask, parallax, and terminal keyline arrangement agree without reading item layout during drag. As items cross keyline boundaries, clip-path: inset(...) transitions them between Large, Medium, and Small sizes.

Scroll behavior by layout

  • multi-browse, hero, center-aligned-hero, and full-screen use mandatory snap-scrolling. Releasing a mouse drag settles at the nearest valid item anchor; touch and wheel input retain native scrolling and momentum.
  • uncontained, uncontained-multi-aspect, and multi-aspect-ratio use free scrolling, so items can stop anywhere in the container.
  • Contained layouts resolve a terminal keyline matrix at the end of the list so the final item does not leave a visual gap after masking.

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.

Item Text Overlay & Scrim (CarouselItemText)

Includes an accessible text overlay with title medium and label small typography over a gradient scrim, matching the official M3 and Figma specifications.

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 { Card, Carousel, CarouselItem, CarouselItemText } from "@bug-on/m3-expressive/layout";

// 1. Standard RSC Mode with Item Text Overlay
<Carousel layout="multi-browse" preferredItemWidth={300} aria-label="Destinations">
  <CarouselItem index={0} totalItems={2}>
    <img src="/photo1.jpg" alt="Mountain" className="w-full h-full object-cover" />
    <CarouselItemText labelText="Mountain Retreat" supportingText="Alpine Adventure" />
  </CarouselItem>
  <CarouselItem index={1} totalItems={2}>
    <img src="/photo2.jpg" alt="Ocean" className="w-full h-full object-cover" />
    <CarouselItemText labelText="Ocean Breeze" supportingText="Coastal Escape" />
  </CarouselItem>
</Carousel>

// 2. XR Spatial Mode with Orbiters
<Carousel
  variant="xr"
  layout="multi-browse"
  showControls="orbiters"
  aria-label="Spatial Gallery"
  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"
  showAllButton
  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.
  • Use variant="xr" in spatial or floating panel contexts with showControls="orbiters".
  • Use CarouselItemText for readability over visual imagery.
  • Use hero layout when spotlighting featured single items.
  • Keep compact contained-carousel text to two lines or fewer; use concise labels when an item reaches the small keyline.

Don't

  • Don't shrink small item widths below 40px (min range: 40px to 56px).
  • Don't force showAllButton unless the user needs to view the complete catalog as a grid.
  • 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
variant"standard" | "xr""standard"Display style variant (standard flat or XR glassmorphic spatial panel).
layoutCarouselLayout"multi-browse"M3 layout variant (multi-browse, uncontained, uncontained-multi-aspect, multi-aspect-ratio, hero, center-aligned-hero, full-screen).
childrenReactNode—RSC Mode: Direct React nodes or server components.
itemsT[]—CSR Mode: Data array.
renderItem(item: T, index: number) => ReactNode—CSR 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.
showAllButtonbooleanfalseWhether to display the "Show all" button.
isExpandedboolean—Controlled state for expanded Grid view.
onShowAllClick() => void—Callback fired when 'Show all' is clicked.
showAllLabelstring"Show all"Label text for 'Show all' button.
showControlsboolean | "auto" | "orbiters"falseShow previous/next buttons (orbiters in XR mode).
prevButtonAriaLabelstring"Previous slide"Accessible label for previous slide button.
nextButtonAriaLabelstring"Next slide"Accessible label for next slide button.
userScrollEnabledbooleantrueEnable or disable user gestures/scrolling.
aria-labelstring"Content carousel"Accessible title/label for carousel container.

CarouselItem

PropTypeDefaultDescription
aspectRatio"16:9" | "4:3" | "1:1" | "3:4" | "9:16" | number—Item aspect ratio constraint.
variant"standard" | "xr""standard"Item variant with optional XR hover/elevation.
disabledbooleanfalseDisables interaction and dims item.

CarouselItemText

PropTypeDefaultDescription
labelTextstring—Primary title text (Title Medium M3 typography).
supportingTextstring—Supporting text (Label Small M3 typography).
showScrimbooleantrueWhether to render the bottom gradient scrim.