feat(Carousel): add new component#190
Merged
johnleider merged 27 commits intomasterfrom Apr 15, 2026
Merged
Conversation
Implements a compound carousel component built on createStep with CSS scroll-snap for native drag/swipe navigation. Supports multi-slide display (perView), partial-slide peeking, circular navigation, and full WAI-ARIA carousel pattern compliance. Components: Root, Viewport, Slide, Previous, Next Tests: 32 passing (registration, navigation, ARIA, data attributes, SSR) Docs: page, basic/multi-slide/peek examples, recipes https://claude.ai/code/session_01LYmjuphJqS2njPANKAoxPr
Follows the pattern from TabsRoot where defineEmits is declared alongside defineModel for 'update:model-value'. https://claude.ai/code/session_01LYmjuphJqS2njPANKAoxPr
|
commit: |
- Fix double-fire bug from v-bind="attrs" on non-renderless children - Fix circular navigation dying with perView > 1 - Add mouse drag with smooth snap-to-slide on release - Add autoplay prop with play/stop/pause/resume controls - Rename CarouselSlide to CarouselItem for consistency - Switch examples to data-attribute CSS selectors
- Move all imports from <script setup> to first <script> block (all 5 files) - Replace manual document.addEventListener with useToggleScope + useDocumentEventListener in Viewport - Add aria-disabled to CarouselNext/Previous (matches Pagination pattern) - Wire up useLocale for all aria-labels (Carousel.next, Carousel.prev, Carousel.slide) - Rename abbreviated variables (idx → index, pv → perView) in CarouselItem - Switch onUnmounted → onBeforeUnmount for unregister cleanup - Add defineEmits comment explaining vue-devtools requirement - Update components.md rules: import enforcement, locale requirement, aria-disabled typing, onBeforeUnmount, defineEmits guidance
gap and peek are styling concerns that break the headless contract. Consumers now control these via CSS (gap, padding utilities) and slide sizing (flex/width classes). The viewport measures actual slide offsets from the DOM for scroll-snap math instead of calculating from prop values.
…ns, fix stale peek references
Previous, Next, and Viewport now register into the parts registry with type and element ref instead of imperative watchEffect push. Extends CarouselContext with autoplay timer state for child consumption.
…rement CarouselItem now registers el on its ticket. CarouselViewport accesses item elements from the registry instead of viewport.children. CarouselRoot uses Atom and extends context with autoplay timer state.
…dicator - Add behavior prop (ScrollBehavior) to control programmatic scroll - Remove scrollbar-width and user-select from viewport inline styles - Remove redundant select method and isUndefined guards from Indicator - Align locale template vars to use size instead of total - Remove unnecessary inline comments from Viewport
…new sub-components
- Add label prop and aria-label to Root region - Add aria-disabled and data-disabled to Root attrs - Convert const arrow functions to function declarations - Fix Indicator step() misuse (absolute vs relative index) - Add Enter/Space keyboard handling to Indicator - Add focus management after Indicator keyboard navigation - Add inheritAttrs: false to LiveRegion - Remove redundant aria-live from Viewport - Export CarouselTicket and CarouselPartTicket types - Fix stale mandatory test describe name - Fix SSR typo in test - Remove since from maturity.json - Update accessibility table in docs
Consumer-controlled gap/peek via CSS classes was too much boilerplate and broke the scroll-snap math. These are structural props tied to scroll behavior, not decorative styling. The component owns the layout: gap on viewport, padding for peek, flex basis on items. Fixed flex basis to not double-subtract peek (padding already reduces the content area, so only gap needs subtracting from the calc).
This reverts commit 6ea7749.
Move scrollbar-width: none and user-select: none (during drag) into the component's inline styles so consumers don't need to add them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new
Carouselcomponent to the semantic components library. This is a headless carousel built on CSS scroll-snap with support for multi-slide display, partial-slide peeking, drag/swipe navigation, and programmatic control via previous/next buttons.Key Changes
CarouselRoot: Root component that creates and provides step context for carousel navigation. Supports circular/bounded modes, orientation (horizontal/vertical), multi-slide display (
perView), gap spacing, and peek configuration.CarouselViewport: Scroll-snap container that handles native drag/swipe interaction and synchronizes scroll position with step selection state. Exposes
isDraggingstate and manages scroll-to-selection synchronization.CarouselSlide: Individual slide component that registers with the carousel context. Provides selection state, visibility tracking, and automatic sizing based on
perView,gap, andpeekvalues.CarouselPrevious & CarouselNext: Navigation button components that automatically disable at carousel boundaries in non-circular mode. Expose
isAtEdgestate for styling.Comprehensive test suite: 1000+ lines of tests covering rendering, navigation (next/prev/first/last), circular/bounded modes, disabled states, mandatory selection, slot props, ARIA attributes, data attributes, and inline styles.
Documentation: Full component documentation with anatomy, examples (basic, multi-slide, peek), recipes (circular, vertical, disabled), accessibility details, and FAQ.
Examples: Three example implementations demonstrating basic carousel, multi-slide display, and peek functionality.
Implementation Details
createStepcomposable for selection managementdata-selected,data-active,data-disabled,data-index) for CSS-only stylingComponent maturity updated from "draft" to "preview" with version 0.3.0.
https://claude.ai/code/session_01LYmjuphJqS2njPANKAoxPr