Carousel
Headless WAI-ARIA carousel on native scroll snapping, with autoplay, drag, and slide picker.
|
|
| Package |
@vizejs/ui/carousel |
| Maturity |
stable |
| Own the source |
vize lib pull carousel |
| Requires |
context, controllable-state, id, measure |
| Aliases |
carousel, slider gallery, slideshow, image carousel, content rotator |
| Covers |
WAI-ARIA Carousel, CSS Scroll Snap, Ark UI Carousel, Embla Carousel, shadcn/ui Carousel |
Usage
import { CarouselAutoplayToggle, CarouselIndicator, CarouselIndicatorGroup, CarouselNext, CarouselPrevious, Carousel, CarouselRoot, CarouselSlide, CarouselViewport } from "@vizejs/ui/carousel";
Or copy the source into your project with vize lib pull carousel (see Source Distribution).
API
CarouselAutoplayToggle
Source: src/families/media/carousel/carousel-autoplay-toggle.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name for icon-only controls. Prefer slot text that changes with the state, e.g. "Stop slide rotation" / "Start slide rotation". |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before toggling. Call preventDefault() to keep the rotation intent. |
Slots
| Slot |
Slot props |
Description |
default |
CarouselSlotState |
Control label. Receives the carousel state; autoplay tells which label to show. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
CarouselIndicator
Source: src/families/media/carousel/carousel-indicator.vue
Props
| Prop |
Type |
Default |
Description |
index (required) |
number |
required |
Zero-based slide index this indicator activates. |
ariaLabel |
string |
undefined |
Accessible name. Defaults to "Slide index + 1" when the slot renders no text. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before activation. Call preventDefault() to keep the active slide. |
Slots
| Slot |
Slot props |
Description |
default |
CarouselIndicatorSlotState |
Indicator content, e.g. a dot or thumbnail. Receives its activity state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
CarouselIndicatorGroup
Source: src/families/media/carousel/carousel-indicator-group.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name of the slide picker, e.g. "Choose slide". |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the slide picker. |
Slots
| Slot |
Slot props |
Description |
default |
CarouselSlotState |
CarouselIndicator children. Receives the carousel state. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered tablist element. |
CarouselNext
Source: src/families/media/carousel/carousel-next.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name for icon-only controls. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before navigation. Call preventDefault() to keep the active slide. |
Slots
| Slot |
Slot props |
Description |
default |
CarouselSlotState |
Control content. Receives the carousel state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
CarouselPrevious
Source: src/families/media/carousel/carousel-previous.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name for icon-only controls. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before navigation. Call preventDefault() to keep the active slide. |
Slots
| Slot |
Slot props |
Description |
default |
CarouselSlotState |
Control content. Receives the carousel state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
Carousel
Source: src/families/media/carousel/carousel-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned carousel base id. null and undefined select a deterministic fallback. |
slideCount (required) |
number |
required |
Number of slides. Declared up front so slide labels ("2 of 5"), navigation availability, and indicators render correctly on the server. |
modelValue |
number |
undefined |
Controlled zero-based active slide index. undefined selects uncontrolled behavior. |
defaultValue |
number |
0 |
Initial active slide for uncontrolled use. |
loop |
boolean |
false |
Wrap previous/next navigation and keyboard movement at both ends. |
orientation |
CarouselOrientation |
"horizontal" |
Scroll axis of the track. |
dir |
CarouselDirection |
undefined |
Reading direction used for horizontal arrow keys and scroll offsets. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
draggable |
boolean |
true |
Let mouse pointers drag the track. Touch and pen swipes always use native scrolling. |
autoplay |
boolean |
false |
Initial automatic rotation intent for uncontrolled use. |
playing |
boolean |
undefined |
Controlled automatic rotation intent (v-model:playing). undefined selects autoplay. |
interval |
number |
5000 |
Milliseconds each slide stays active while rotating. |
pauseOnHover |
boolean |
true |
Pause rotation while a mouse or pen pointer hovers the carousel. |
focusBehavior |
CarouselFocusBehavior |
"stop" |
How keyboard focus entering the carousel affects rotation. stop follows the WAI-ARIA carousel pattern and turns rotation off until explicitly restarted. |
respectReducedMotion |
boolean |
true |
Pause rotation while prefers-reduced-motion: reduce matches, and scroll without smooth animation. |
ariaLabel |
string |
undefined |
Accessible carousel name when no visible heading supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the carousel. |
messages |
CarouselMessages |
undefined |
Localized role descriptions and slide/indicator names. Omitted entries use the English WAI-ARIA example wording. |
Events
| Event |
Payload |
Description |
update:modelValue |
[index: number] |
Fired when the active slide requests a new controlled index. |
change |
[index: number, previous: number, reason: CarouselChangeReason] |
Fired after any distinct active-slide request, with its cause. |
update:playing |
[playing: boolean] |
Fired when rotation intent requests a new controlled value. |
Slots
| Slot |
Slot props |
Description |
default |
CarouselSlotState |
Viewport, slides, controls, and indicators. Receives the carousel state. |
Exposed
| Member |
Type |
Description |
autoplay |
— |
|
canScrollNext |
— |
|
canScrollPrev |
— |
|
dragging |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
index |
— |
|
orientation |
— |
|
play |
— |
|
scrollNext |
— |
|
scrollPrev |
— |
|
scrollTo |
— |
|
slideCount |
— |
|
stop |
— |
|
CarouselRoot
Source: src/families/media/carousel/carousel-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned carousel base id. null and undefined select a deterministic fallback. |
slideCount (required) |
number |
required |
Number of slides. Declared up front so slide labels ("2 of 5"), navigation availability, and indicators render correctly on the server. |
modelValue |
number |
undefined |
Controlled zero-based active slide index. undefined selects uncontrolled behavior. |
defaultValue |
number |
0 |
Initial active slide for uncontrolled use. |
loop |
boolean |
false |
Wrap previous/next navigation and keyboard movement at both ends. |
orientation |
CarouselOrientation |
"horizontal" |
Scroll axis of the track. |
dir |
CarouselDirection |
undefined |
Reading direction used for horizontal arrow keys and scroll offsets. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
draggable |
boolean |
true |
Let mouse pointers drag the track. Touch and pen swipes always use native scrolling. |
autoplay |
boolean |
false |
Initial automatic rotation intent for uncontrolled use. |
playing |
boolean |
undefined |
Controlled automatic rotation intent (v-model:playing). undefined selects autoplay. |
interval |
number |
5000 |
Milliseconds each slide stays active while rotating. |
pauseOnHover |
boolean |
true |
Pause rotation while a mouse or pen pointer hovers the carousel. |
focusBehavior |
CarouselFocusBehavior |
"stop" |
How keyboard focus entering the carousel affects rotation. stop follows the WAI-ARIA carousel pattern and turns rotation off until explicitly restarted. |
respectReducedMotion |
boolean |
true |
Pause rotation while prefers-reduced-motion: reduce matches, and scroll without smooth animation. |
ariaLabel |
string |
undefined |
Accessible carousel name when no visible heading supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the carousel. |
messages |
CarouselMessages |
undefined |
Localized role descriptions and slide/indicator names. Omitted entries use the English WAI-ARIA example wording. |
Events
| Event |
Payload |
Description |
update:modelValue |
[index: number] |
Fired when the active slide requests a new controlled index. |
change |
[index: number, previous: number, reason: CarouselChangeReason] |
Fired after any distinct active-slide request, with its cause. |
update:playing |
[playing: boolean] |
Fired when rotation intent requests a new controlled value. |
Slots
| Slot |
Slot props |
Description |
default |
CarouselSlotState |
Viewport, slides, controls, and indicators. Receives the carousel state. |
Exposed
| Member |
Type |
Description |
index |
number |
Zero-based active slide index. |
slideCount |
number |
Total number of slides declared on the root. |
canScrollPrev |
boolean |
Whether previous navigation is available. |
canScrollNext |
boolean |
Whether next navigation is available. |
autoplay |
CarouselAutoplayState |
Automatic rotation state. |
dragging |
boolean |
Whether a pointer drag is moving the track. |
orientation |
CarouselOrientation |
Scroll axis. |
element |
HTMLElement | null |
Rendered root element. |
scrollTo |
(index: number) => boolean |
Activate one slide. Reports whether the active index changed. |
scrollNext |
() => boolean |
Activate the next slide (wrapping when loop). Reports whether the index changed. |
scrollPrev |
() => boolean |
Activate the previous slide (wrapping when loop). Reports whether the index changed. |
play |
() => boolean |
Turn automatic rotation on. Reports whether the intent changed. |
stop |
() => boolean |
Turn automatic rotation off. Reports whether the intent changed. |
CarouselSlide
Source: src/families/media/carousel/carousel-slide.vue
Props
| Prop |
Type |
Default |
Description |
index (required) |
number |
required |
Zero-based slide position. Must be unique and below slideCount. |
ariaLabel |
string |
undefined |
Accessible slide name. Defaults to "index + 1 of slideCount" per the carousel pattern. |
Slots
| Slot |
Slot props |
Description |
default |
CarouselSlideSlotState |
Slide content. Receives activity and visibility state. |
Exposed
| Member |
Type |
Description |
index |
number |
Zero-based slide index. |
active |
boolean |
Whether this slide is the active slide. |
inView |
boolean | null |
Whether the slide is at least half visible; null until measured on the client. |
state |
CarouselSlideState |
Stable state token for styling and tests. |
element |
HTMLDivElement | null |
Rendered slide element. |
id |
string |
Deterministic slide id. |
CarouselViewport
Source: src/families/media/carousel/carousel-viewport.vue
Props
| Prop |
Type |
Default |
Description |
settleDelay |
number |
120 |
Quiet period after the last scroll event before a user scroll selects the nearest slide, for browsers without scrollend. |
dragThreshold |
number |
40 |
Pixels a mouse drag must travel to page to the adjacent slide even when the track would snap back to the starting slide. |
Slots
| Slot |
Slot props |
Description |
default |
CarouselSlotState |
CarouselSlide children. Receives the carousel state. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered scroll container. |
id |
string |
Deterministic id referenced by navigation controls. |
Behavior
Normative state x input -> outcome table for carousel-root.vue,
carousel-viewport.vue, carousel-slide.vue, carousel-previous.vue,
carousel-next.vue, carousel-indicator-group.vue, carousel-indicator.vue, and
carousel-autoplay-toggle.vue (@vizejs/ui/carousel). Every row is proven by
the named test.
The carousel follows the WAI-ARIA APG carousel pattern on top of native scroll
snapping: the viewport is the consumer-styled scroll container, the active index
is the source of truth, and the track scrolls to the active slide. User scrolls
(touch swipes, trackpads, scrollbars) settle on the nearest slide without being
corrected. slideCount and each slide's index are declared so labels,
navigation availability, and indicators are correct on the server.
Minimal consumer CSS: [data-vize-ui="carousel-viewport"] { display: flex; overflow: auto; scroll-snap-type: x mandatory } and
[data-vize-ui="carousel-slide"] { flex: 0 0 100%; scroll-snap-align: start }.
| ID |
State |
Input |
Outcome |
Evidence |
| C1 |
default |
render |
section with aria-roledescription="carousel", focusable polite viewport, group/slide slides labelled "n of N", wired controls and tablist |
renders the carousel pattern with deterministic ids, slide labels, and wired controls |
| C2 |
non-looping |
Previous / Next |
moves one slide, smooth-scrolls the viewport so the slide aligns with its start edge, and disables the control at each end |
previous and next move one slide, scroll the track, and stop at the ends |
| C3 |
loop |
Previous at start / Next at end |
wraps to the other end; a single slide never wraps |
loop wraps navigation at both ends |
| C4 |
controlled modelValue |
navigation |
emits the requested index while rendering the controlled index; out-of-range values clamp |
controlled index wins until the parent accepts the request |
| C5 |
indicators |
click / Arrow / Home / End |
selects the slide, keeps one roving tab stop on the active indicator, and moves focus with the selection |
indicators select slides and move roving focus with arrow, Home, and End keys |
| C6 |
focused viewport |
Arrow / Home / End |
arrows follow orientation and dir; keys from slide content are ignored |
the focused viewport maps arrow keys by orientation and reading direction |
| C7 |
user scroll |
scroll quiet period or scrollend |
the slide nearest the viewport start becomes active with reason scroll, without a corrective scroll |
user scrolling settles on the nearest slide without re-scrolling |
| C8 |
programmatic scroll |
own scrollend / reduced motion |
initial defaultValue scrolls instantly; programmatic settles are ignored; reduced motion scrolls with behavior: "auto" |
programmatic scrolls ignore their own settle and respect reduced motion |
| C9 |
multiple slides per view |
scrolled to the end |
Next disables once the track cannot scroll further |
scroll edges disable next when multiple slides fill the viewport |
| C10 |
mouse drag |
pointerdown / move / up |
moves the track with snapping suspended, pages past dragThreshold, re-snaps, and swallows the trailing click; touch and draggable=false skip |
mouse drags move the track, page past the threshold, and suppress the trailing click |
| C11 |
short drag |
pointer release below the threshold |
re-aligns to the starting slide without a change event |
short drags snap back to the starting slide |
| C12 |
autoplay playing |
interval elapses |
advances with reason autoplay, rewinds after the last slide, and sets the viewport live region to off |
autoplay advances on an interval, rewinds at the end, and silences the live region |
| C13 |
autoplay playing |
hover / pointer focus / keyboard focus |
mouse hover pauses; pointer-initiated focus does not stop; keyboard focus stops (focusBehavior="stop") or pauses ("pause"); touch never hovers |
hover and pointer focus pause rotation while keyboard focus stops it |
| C14 |
autoplay playing |
reduced motion / hidden document |
prefers-reduced-motion and document.visibilityState="hidden" pause rotation unless respectReducedMotion=false |
reduced motion and hidden documents pause rotation |
| C15 |
controlled playing |
toggle / single slide |
emits update:playing while the controlled intent wins; one slide never rotates and disables the toggle |
controlled playing and single-slide carousels keep rotation stopped |
| C16 |
measured visibility |
intersection changes |
slides under half visible become inert (never the active slide) and expose data-in-view |
slides measured out of view become inert while the active slide stays reachable |
| C17 |
controls |
click with preventDefault() |
Previous, Next, indicators, and the autoplay toggle leave state unchanged |
controls honor preventDefault from click listeners |
| C18 |
exposed instance |
read / imperative calls |
exposes state and scrollTo, scrollNext, scrollPrev, play, stop, each reporting whether state changed |
exposes typed state and imperative navigation and rotation controls |
| C19 |
missing provider |
setup |
compound parts fail closed with the shared context diagnostic |
compound parts require a matching root provider |
| C20 |
geometry |
pure helpers |
index clamping/wrapping, ltr/rtl/vertical offsets, nearest-slide selection, scroll edges, and drag paging are deterministic |
carousel-geometry.test.ts |
| C21 |
SSR |
isolated requests |
markup is byte-identical, reflects defaultValue and autoplay, and never renders inert |
renders byte-identical carousel markup across isolated SSR requests |
| C22 |
SSR / hydration |
hydrate |
server markup hydrates without warnings or node replacement |
hydrates carousel markup without warnings or node replacement |
| C23 |
types |
compile |
states, reasons, slot state, and exposes are closed and read-only |
carousel.types.test-d.ts |
| C24 |
messages |
render |
role descriptions and slide/indicator names come from the typed messages prop; omitted entries keep the English WAI-ARIA wording |
messages localize role descriptions and slide and indicator names |
Timers and listeners are client-only: they start in onMounted and stop on
unmount, so server rendering never schedules rotation.