carousel-wc

Scroll carousel with prev/next buttons, dot indicators, autoplay, keyboard navigation, and ARIA.

Overview

A scroll-snap carousel with full progressive enhancement. Without JavaScript it renders as a simple scrollable row. With JS it adds prev/next buttons, dot indicators, autoplay, looping, keyboard navigation, and ARIA roles.

Usage

Wrap slide elements inside <carousel-wc>. Each direct child becomes a slide.

Slide 1
Slide 2
Slide 3
<carousel-wc> <div>Slide 1</div> <div>Slide 2</div> <div>Slide 3</div> </carousel-wc>

Autoplay with Looping

Add data-autoplay and data-loop to cycle slides automatically. Autoplay pauses on hover and focus, and is disabled entirely when prefers-reduced-motion is active.

<carousel-wc data-autoplay data-loop data-autoplay-delay="3000"> <div>Slide A</div> <div>Slide B</div> <div>Slide C</div> </carousel-wc>

Multi-item Carousel

Set data-layout-item-width="auto" to show multiple items at once with a gap.

<carousel-wc data-layout-item-width="auto" data-layout-gap="m" data-loop> <div class="card">Card 1</div> <div class="card">Card 2</div> <div class="card">Card 3</div> <div class="card">Card 4</div> </carousel-wc>

No Indicators

Set data-indicators="false" to hide dot indicators.

<carousel-wc data-indicators="false"> <div>Slide 1</div> <div>Slide 2</div> </carousel-wc>

Attributes

AttributeValuesDefaultDescription
data-autoplaybooleanEnable autoplay
data-autoplay-delaynumber (ms)5000Autoplay interval
data-loopbooleanWrap around at ends
data-indicators"true", "false""true"Show dot indicators
data-item-widthfull, auto, CSS lengthfullSlide width
data-gapxs, s, m, l, xlGap between slides
data-startnumber0Initial slide index
data-persiststring keylocalStorage key for slide persistence
data-transitionfade, slide, scaleEnable View Transition animations (switches to stacked-grid layout)

Events

EventDetailDescription
carousel-change{ index, slide }Fired when the active slide changes.
carousel-play{ }Fired when autoplay starts.
carousel-pause{ }Fired when autoplay pauses.
const carousel = document.querySelector('carousel-wc'); carousel.addEventListener('carousel-change', (e) => { console.log('Slide:', e.detail.index); }); carousel.addEventListener('carousel-play', () => { console.log('Autoplay started'); }); carousel.addEventListener('carousel-pause', () => { console.log('Autoplay paused'); });

JavaScript API

Property / MethodTypeDescription
element.currentIndexnumberCurrent slide index (read-only)
element.slideCountnumberTotal number of slides (read-only)
element.playingbooleanWhether autoplay is active (read-only)
element.next()methodGo to next slide
element.prev()methodGo to previous slide
element.goTo(index)methodJump to specific slide
element.play()methodStart autoplay
element.pause()methodPause autoplay
element.reset()methodReturn to initial slide, clear persistence
const el = document.querySelector('carousel-wc'); el.next(); // Go to next slide el.prev(); // Go to previous slide el.goTo(2); // Jump to slide index 2 console.log(el.currentIndex); // Current slide index console.log(el.slideCount); // Total slides console.log(el.playing); // Autoplay active? el.pause(); // Pause autoplay el.play(); // Resume autoplay el.reset(); // Return to initial slide

Keyboard Navigation

Focus the carousel track (Tab into it), then use arrow keys:

KeyAction
ArrowLeftPrevious slide
ArrowRightNext slide
HomeFirst slide
EndLast slide

Accessibility

ARIA Roles

The component uses role="region" with aria-roledescription="carousel". Each slide has role="group" with aria-roledescription="slide" and a label like "1 of 4". Dot indicators use role="tablist" and role="tab".

Live Region

A visually hidden aria-live="polite" region announces slide changes to screen readers.

Reduced Motion

When prefers-reduced-motion is active, autoplay is completely disabled and scroll behavior uses instant transitions.

Styling

Override the button and dot styles using the .carousel-prev, .carousel-next, and .carousel-dot classes.

/* Custom button styling */ .carousel-prev, .carousel-next { background: var(--color-interactive); color: white; border: none; } /* Custom dot styling */ .carousel-dot[data-active] { background: var(--color-primary); }

Progressive Enhancement

Without JavaScript, <carousel-wc> renders as a horizontal scrollable row using the :not(:defined) fallback. Once JS registers the component, controls and indicators appear.

/* Without JS: simple horizontal scroll */ carousel-wc:not(:defined) { display: flex; overflow-x: auto; gap: var(--size-s); scroll-snap-type: x mandatory; } carousel-wc:not(:defined) > * { flex: 0 0 auto; scroll-snap-align: start; }

View Transitions

Add data-transition to switch from scroll-snap to View Transition animations. When active, the carousel uses a stacked-grid layout and animates between slides using the View Transitions API.

ValueEffect
fade (default)Crossfade between slides
slideDirectional slide — forward when advancing, backward when retreating
scaleScale down old slide, scale up new slide

Dual-Mode Behavior

Without data-transition, the carousel uses scroll-snap with IntersectionObserver — slides are arranged in a horizontal row and the user scrolls between them. With data-transition, the carousel switches to a stacked CSS Grid layout where slides occupy the same grid cell and are shown/hidden with View Transition animations.

Slide 1

Directional slide transitions

Slide 2

Forward and backward animations

Slide 3

Keyboard navigation works too

Related