feat(carousel): implement Material 3 Carousel component - #122
Conversation
|
/gemini-review |
1 similar comment
|
/gemini-review |
There was a problem hiding this comment.
📋 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.
| const forwards = isRtlMode ? isLeft : isRight | ||
| const backwards = isRtlMode ? isRight : isLeft |
There was a problem hiding this comment.
🟡 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.
| const scroller = this.scrollerElement | ||
| const scrollerRect = scroller.getBoundingClientRect() |
There was a problem hiding this comment.
🟢 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.
| const scroller = this.scrollerElement | |
| const scrollerRect = scroller.getBoundingClientRect() | |
| const scroller = this.scrollerElement | |
| const scrollerRect = scroller.getBoundingClientRect() |
Deploying with
|
| 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 |
There was a problem hiding this comment.
📋 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.
| renderTextContent() { | ||
| const hasText = this.headline || this.subhead | ||
| return html` |
There was a problem hiding this comment.
🟢 The local constant hasText is declared but never referenced in renderTextContent().
| renderTextContent() { | |
| const hasText = this.headline || this.subhead | |
| return html` | |
| renderTextContent() { | |
| return html` |
|
|
||
| let scrollTarget | ||
| if (this.layout === 'centered-hero') { |
There was a problem hiding this comment.
🟢 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.
| let scrollTarget | |
| if (this.layout === 'centered-hero') { | |
| const scroller = this.scrollerElement |
| _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 | ||
| } |
There was a problem hiding this comment.
🟡 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.
| _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 {} | |
| } |
There was a problem hiding this comment.
📋 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
scrollerRectanditemRectdeclarations inscrollToIndex. (carousel/carousel.js:266) - Removed unused
hasTextconstant inrenderTextContent. (carousel/carousel-item.js:99) - Added pointer capture (
setPointerCaptureandreleasePointerCapture) 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", andaria-roledescription="slide"attributes) and RTL direction handling are well integrated. - The interactive demo in
carousel-demo.htmlis 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.
…k] multi-browse layout
There was a problem hiding this comment.
📋 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 (
itemRectandscrollerRect) inscrollToIndex. (carousel/carousel.js:266) - Implemented pointer capture (
setPointerCaptureandreleasePointerCapture) during drag interactions to ensure pointer tracking is not lost outside the container. (carousel/carousel.js:554) - Removed unused local variable
hasTextinrenderTextContent(). (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.
There was a problem hiding this comment.
📋 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.
There was a problem hiding this comment.
📋 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_onPointerDownto prevent losing pointer events during drag gestures. (carousel/carousel.js:560) - Removed redundant
scrollerRectanditemRectvariable declarations fromscrollToIndex. (carousel/carousel.js:268) - Removed unused
hasTextvariable fromrenderTextContentinCarouselItem. (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.
| } | ||
|
|
||
| render() { | ||
| const isRtlMode = isRtl(this, false) |
There was a problem hiding this comment.
🟠 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.
| const isRtlMode = isRtl(this, false) | |
| const isRtlMode = isRtl(this) |
| _handleKeyDown(event) { | ||
| if (event.defaultPrevented) return | ||
|
|
||
| const isRtlMode = isRtl(this, false) |
There was a problem hiding this comment.
🟠 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.
| const isRtlMode = isRtl(this, false) | |
| const isRtlMode = isRtl(this) |
Description
Implements the Material 3 Carousel web component based on the official Material Design 3 Carousel specification.
Features
<md-carousel>:multi-browse(default dynamic sizing),uncontained,hero,centered-hero, andfull-screen.indicators).autoplay="3000").ResizeObserver.<md-carousel-item>:var(--md-sys-shape-corner-extra-large, 28px)default), card variants (filled,elevated,outlined), and ripple/focus-ring interactivity.headline,subhead,scrim, and actions.all.jsandcommon.js.carousel/README.md.demo/carousel-demo.html.demo/index.html.Verification