Skip to content

feat(carousel): implement Material 3 Carousel component - #122

Merged
treeder merged 6 commits into
mainfrom
feat/material-3-carousel
Aug 22, 2026
Merged

feat(carousel): implement Material 3 Carousel component#122
treeder merged 6 commits into
mainfrom
feat/material-3-carousel

Conversation

@treeder

@treeder treeder commented Aug 21, 2026

Copy link
Copy Markdown
Member

Description

Implements the Material 3 Carousel web component based on the official Material Design 3 Carousel specification.

Features

  • <md-carousel>:
    • Supports all 5 M3 layout strategies: multi-browse (default dynamic sizing), uncontained, hero, centered-hero, and full-screen.
    • Floating previous and next navigation icon buttons with chevron icons and boundary disabling.
    • Pagination indicator dots with active pill styling (indicators).
    • Pointer drag gestures (mouse/touch) and native CSS scroll-snapping.
    • Autoplay support with auto-pause on hover/focus and resume (autoplay="3000").
    • Full keyboard navigation (Arrow keys, Home/End) and ARIA accessibility roles.
    • Dynamic responsive sizing with ResizeObserver.
  • <md-carousel-item>:
    • Corner shapes (var(--md-sys-shape-corner-extra-large, 28px) default), card variants (filled, elevated, outlined), and ripple/focus-ring interactivity.
    • Overflow clipping for images/video and slots for headline, subhead, scrim, and actions.
  • Exports & Documentation:
    • Exported in all.js and common.js.
    • Comprehensive documentation in carousel/README.md.
  • Demos:
    • Interactive playground and full showcase in demo/carousel-demo.html.
    • Linked and integrated into demo/index.html.

Verification

  • Verified in headless Chrome via Playwright across all 5 layouts, button navigation, indicator jumping, and layout switching with 0 console errors.

@treeder

treeder commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

/gemini-review

1 similar comment
@treeder

treeder commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

/gemini-review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

This pull request introduces the Material Design 3 Carousel component (md-carousel and md-carousel-item), implementing multi-browse, uncontained, hero, centered-hero, and full-screen layout strategies. It includes comprehensive documentation, interactive playground demos, touch/pointer dragging support, autoplay with hover/focus pause, and integration into the main library entry points.

🔍 General Feedback

  • The component implementation is clean, well-structured, and aligns well with existing Lit component architecture in the repository.
  • Comprehensive coverage of Material 3 Carousel specifications with dynamic multi-browse sizing, keyboard accessibility, ARIA slide landmarks, and responsive layouts.
📊 Token Usage & Cost Efficiency
Metric Value
Input Tokens (uncached) 55,857
Input Tokens (cached) 325,505 (⚡ 85.3% cached)
PR Comments History Tokens 103
Output Tokens 574
Total Session Tokens 382,258
Cost (uncached input) $0.0420
Cost (cached input) $0.0244
Cost (output) $0.0022
Estimated Total Cost $0.0685

Gemini 3.7 Flash: introductory rate $0.75/$3.75 per 1M applied; reverts to $1.5/$7.5 after 2026-12-31.
Gemini 3.7 Flash: standard tier; batch and flex are half again, priority is higher.
Cache reads are priced here, but context-cache STORAGE, where the model charges for it separately per token-hour, is not included, so the figure can run slightly low.

Comment thread carousel/carousel.js
Comment on lines +402 to +403
const forwards = isRtlMode ? isLeft : isRight
const backwards = isRtlMode ? isRight : isLeft

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The variable assignments for forwards and backwards inverted the direction check for RTL mode. isRtlMode ? isLeft : isRight means when moving right in RTL it is not considered forwards, but ArrowLeft in RTL is forwards. However, isLeft and isRight are boolean flags for key codes, so forwards evaluates to true when event.key === 'ArrowLeft' in RTL and event.key === 'ArrowRight' in LTR. backwards evaluates to true when event.key === 'ArrowRight' in RTL and event.key === 'ArrowLeft' in LTR. This is correct logic, but assigning them as booleans and then testing else if (forwards) is clean. Ensure event.defaultPrevented check on line 397 doesn't block custom key bindings if consumers cancel them.

Comment thread carousel/carousel.js Outdated
Comment on lines +260 to +261
const scroller = this.scrollerElement
const scrollerRect = scroller.getBoundingClientRect()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 In scrollToIndex, scrollerRect and itemRect are computed via getBoundingClientRect(), but itemRect is unused in the calculation (using offsetLeft and offsetWidth instead). You can remove the unused itemRect declaration.

Suggested change
const scroller = this.scrollerElement
const scrollerRect = scroller.getBoundingClientRect()
const scroller = this.scrollerElement
const scrollerRect = scroller.getBoundingClientRect()

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 22, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
material 99378c5 Commit Preview URL

Branch Preview URL
Aug 22 2026, 06:49 PM

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

This pull request introduces the Material Design 3 Carousel component family (<md-carousel> and <md-carousel-item>), complete with multi-browse, uncontained, hero, centered-hero, and full-screen layout strategies. It adds comprehensive documentation, interactive demo pages, keyboard navigation, drag/swipe gestures, and integration with all.js and common.js bundles. Overall, the implementation is high quality, well-structured, and closely aligns with M3 design specifications.

🔍 General Feedback

  • The component implementation is comprehensive, providing robust support for multiple M3 carousel layouts, fluid responsive scaling, scroll-snapping, and pagination indicators.
  • Documentation and demo pages are well-crafted with practical examples across multiple layout modes.
  • Accessibility attributes (such as role="region", aria-roledescription="carousel", and slide group labels) are well integrated.
📊 Token Usage & Cost Efficiency
Metric Value
Input Tokens (uncached) 55,857
Input Tokens (cached) 326,012 (⚡ 85.2% cached)
PR Comments History Tokens 703
Output Tokens 724
Total Session Tokens 387,107
Cost (uncached input) $0.0424
Cost (cached input) $0.0245
Cost (output) $0.0027
Estimated Total Cost $0.0696

Gemini 3.7 Flash: introductory rate $0.75/$3.75 per 1M applied; reverts to $1.5/$7.5 after 2026-12-31.
Gemini 3.7 Flash: standard tier; batch and flex are half again, priority is higher.
Cache reads are priced here, but context-cache STORAGE, where the model charges for it separately per token-hour, is not included, so the figure can run slightly low.

Comment thread carousel/carousel-item.js
Comment on lines +96 to +98
renderTextContent() {
const hasText = this.headline || this.subhead
return html`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 The local constant hasText is declared but never referenced in renderTextContent().

Suggested change
renderTextContent() {
const hasText = this.headline || this.subhead
return html`
renderTextContent() {
return html`

Comment thread carousel/carousel.js
Comment on lines +263 to +265

let scrollTarget
if (this.layout === 'centered-hero') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 scrollerRect and itemRect are computed via getBoundingClientRect(), but neither is referenced in scrollToIndex (the target offset calculation uses targetItem.offsetLeft, targetItem.offsetWidth, and scroller.offsetWidth). Removing them avoids redundant DOM layout measurements.

Suggested change
let scrollTarget
if (this.layout === 'centered-hero') {
const scroller = this.scrollerElement

Comment thread carousel/carousel.js
Comment on lines +416 to +425
_onPointerDown(e) {
if (e.button !== 0) return // Left-click only
const scroller = this.scrollerElement
if (!scroller) return

this._isPointerDown = true
this._startX = e.pageX - scroller.offsetLeft
this._startScrollLeft = scroller.scrollLeft
this._hasDragged = false
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Consider capturing the pointer using setPointerCapture(e.pointerId) during _onPointerDown. If the user drags swiftly and moves the cursor outside the scroller boundary, the pointerup event may be lost, leaving the carousel in an active dragging state.

Suggested change
_onPointerDown(e) {
if (e.button !== 0) return // Left-click only
const scroller = this.scrollerElement
if (!scroller) return
this._isPointerDown = true
this._startX = e.pageX - scroller.offsetLeft
this._startScrollLeft = scroller.scrollLeft
this._hasDragged = false
}
_onPointerDown(e) {
if (e.button !== 0) return // Left-click only
const scroller = this.scrollerElement
if (!scroller) return
this._isPointerDown = true
this._startX = e.pageX - scroller.offsetLeft
this._startScrollLeft = scroller.scrollLeft
this._hasDragged = false
try {
scroller.setPointerCapture?.(e.pointerId)
} catch {}
}

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

This pull request introduces the Material Design 3 Carousel component (<md-carousel> and <md-carousel-item>) with support for multi-browse, uncontained, hero, centered-hero, and full-screen layout strategies. It includes comprehensive documentation in carousel/README.md, an interactive demo page in demo/carousel-demo.html, bundle exports in all.js and common.js, and integration into the main demo.

✅ Resolved Items from Prior Reviews

  • Removed unused scrollerRect and itemRect declarations in scrollToIndex. (carousel/carousel.js:266)
  • Removed unused hasText constant in renderTextContent. (carousel/carousel-item.js:99)
  • Added pointer capture (setPointerCapture and releasePointerCapture) during pointer down and up/cancel to prevent drag gestures from getting stuck when moving outside bounds. (carousel/carousel.js:577)

🔍 General Feedback

  • The carousel implementation adheres closely to the Material Design 3 specifications with support for responsive item scaling, scroll-snapping, and smooth keyboard/pointer interactions.
  • Accessibility features (such as aria-roledescription="carousel", role="group", and aria-roledescription="slide" attributes) and RTL direction handling are well integrated.
  • The interactive demo in carousel-demo.html is comprehensive and effectively demonstrates each layout strategy and configuration option.
📊 Token Usage & Cost Efficiency
Metric Value
Input Tokens (uncached) 61,203
Input Tokens (cached) 326,012 (⚡ 84.0% cached)
PR Comments History Tokens 1,083
Output Tokens 404
Total Session Tokens 389,570
Cost (uncached input) $0.0467
Cost (cached input) $0.0245
Cost (output) $0.0015
Estimated Total Cost $0.0727

Gemini 3.7 Flash: introductory rate $0.75/$3.75 per 1M applied; reverts to $1.5/$7.5 after 2026-12-31.
Gemini 3.7 Flash: standard tier; batch and flex are half again, priority is higher.
Cache reads are priced here, but context-cache STORAGE, where the model charges for it separately per token-hour, is not included, so the figure can run slightly low.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

This pull request introduces the Material Design 3 <md-carousel> and <md-carousel-item> components along with documentation, demo pages, and exports in all.js and common.js. The implementation supports five distinct layout strategies (multi-browse, uncontained, hero, centered-hero, and full-screen), fluid responsive sizing, pointer drag scrolling, keyboard navigation, autoplay, and pagination indicators.

✅ Resolved Items from Prior Reviews

  • Removed unused getBoundingClientRect calculations (itemRect and scrollerRect) in scrollToIndex. (carousel/carousel.js:266)
  • Implemented pointer capture (setPointerCapture and releasePointerCapture) during drag interactions to ensure pointer tracking is not lost outside the container. (carousel/carousel.js:554)
  • Removed unused local variable hasText in renderTextContent(). (carousel/carousel-item.js:99)

🔍 General Feedback

  • The component implementation cleanly follows Material Design 3 guidelines for carousels, with robust layout calculations across varying container dimensions.
  • Comprehensive documentation and interactive demos have been provided across all five layout variants.
📊 Token Usage & Cost Efficiency
Metric Value
Input Tokens (uncached) 60,396
Input Tokens (cached) 326,012 (⚡ 84.1% cached)
PR Comments History Tokens 1,079
Output Tokens 333
Total Session Tokens 389,599
Cost (uncached input) $0.0461
Cost (cached input) $0.0245
Cost (output) $0.0012
Estimated Total Cost $0.0718

Gemini 3.7 Flash: introductory rate $0.75/$3.75 per 1M applied; reverts to $1.5/$7.5 after 2026-12-31.
Gemini 3.7 Flash: standard tier; batch and flex are half again, priority is higher.
Cache reads are priced here, but context-cache STORAGE, where the model charges for it separately per token-hour, is not included, so the figure can run slightly low.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

This pull request introduces the Material Design 3 Carousel component (<md-carousel> and <md-carousel-item>), supporting multi-browse, uncontained, hero, centered-hero, and full-screen layout strategies. It includes comprehensive documentation, responsive dynamic item sizing, drag and touch interactions, keyboard navigation, and interactive demo pages.

✅ Resolved Items from Prior Reviews

  • Removed unused 'scrollerRect' and 'itemRect' measurements from 'scrollToIndex' in 'carousel.js'. (carousel/carousel.js:266)
  • Removed unused 'hasText' constant declaration in 'carousel-item.js'. (carousel/carousel-item.js:99)
  • Added pointer capture with 'setPointerCapture' in '_onPointerDown' and release in '_onPointerUp'/'_onPointerCancel' to handle fast drag motions robustly. (carousel/carousel.js:554)

🔍 General Feedback

  • The carousel implementation adheres closely to the Material 3 design specifications with dynamic large/medium/small item resizing during multi-browse and hero layouts.
  • Comprehensive exports added across 'all.js' and 'common.js', along with thorough component documentation and an interactive demo page.
  • Accessible keyboard navigation (Arrow keys, Home, End) and ARIA attributes ('roledescription="carousel"', 'roledescription="slide"', 'role="group"') are properly implemented.
📊 Token Usage & Cost Efficiency
Metric Value
Input Tokens (uncached) 61,465
Input Tokens (cached) 326,012 (⚡ 83.9% cached)
PR Comments History Tokens 1,080
Output Tokens 391
Total Session Tokens 391,414
Cost (uncached input) $0.0469
Cost (cached input) $0.0245
Cost (output) $0.0015
Estimated Total Cost $0.0728

Gemini 3.7 Flash: introductory rate $0.75/$3.75 per 1M applied; reverts to $1.5/$7.5 after 2026-12-31.
Gemini 3.7 Flash: standard tier; batch and flex are half again, priority is higher.
Cache reads are priced here, but context-cache STORAGE, where the model charges for it separately per token-hour, is not included, so the figure can run slightly low.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

This pull request introduces the Material 3 Carousel component suite (<md-carousel> and <md-carousel-item>) with full support for multi-browse, uncontained, hero, centered-hero, and full-screen layout strategies. It adds documentation, an interactive demo showcase, and exports the new components via all.js and common.js.

✅ Resolved Items from Prior Reviews

  • Added pointer capture via setPointerCapture(e.pointerId) during _onPointerDown to prevent losing pointer events during drag gestures. (carousel/carousel.js:560)
  • Removed redundant scrollerRect and itemRect variable declarations from scrollToIndex. (carousel/carousel.js:268)
  • Removed unused hasText variable from renderTextContent in CarouselItem. (carousel/carousel-item.js:99)

🔍 General Feedback

  • Great implementation of the Material 3 Carousel specification with dynamic resizing across multi-browse, hero, and centered-hero layouts.
  • Keyboard navigation (Arrow keys, Home, End), ARIA roles (region, carousel, group, slide), and responsive ResizeObserver integration are well constructed.
  • The interactive demo page (carousel-demo.html) provides a comprehensive showcase of layout variations and controls.
📊 Token Usage & Cost Efficiency
Metric Value
Input Tokens (uncached) 61,573
Input Tokens (cached) 326,012 (⚡ 83.9% cached)
PR Comments History Tokens 1,080
Output Tokens 628
Total Session Tokens 392,334
Cost (uncached input) $0.0470
Cost (cached input) $0.0245
Cost (output) $0.0024
Estimated Total Cost $0.0738

Gemini 3.7 Flash: introductory rate $0.75/$3.75 per 1M applied; reverts to $1.5/$7.5 after 2026-12-31.
Gemini 3.7 Flash: standard tier; batch and flex are half again, priority is higher.
Cache reads are priced here, but context-cache STORAGE, where the model charges for it separately per token-hour, is not included, so the figure can run slightly low.

Comment thread carousel/carousel.js
}

render() {
const isRtlMode = isRtl(this, false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 isRtl(this, false) disables the direction check because the second argument (shouldCheck) is explicitly set to false, causing isRtlMode to always evaluate to false. Omitting the second argument (which defaults to true) ensures right-to-left contexts are accurately detected.

Suggested change
const isRtlMode = isRtl(this, false)
const isRtlMode = isRtl(this)

Comment thread carousel/carousel.js
_handleKeyDown(event) {
if (event.defaultPrevented) return

const isRtlMode = isRtl(this, false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Passing false as the second argument to isRtl(this, false) suppresses the RTL check, preventing arrow keys from reversing direction in RTL mode. Call isRtl(this) instead.

Suggested change
const isRtlMode = isRtl(this, false)
const isRtlMode = isRtl(this)

@treeder
treeder merged commit b182864 into main Aug 22, 2026
2 checks passed
@treeder
treeder deleted the feat/material-3-carousel branch August 22, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant