Carousel

A scrolling track of slides with controls.

Usage

Built on CSS scroll-snap, not transforms. Touch swiping, trackpad flicks, momentum and the browser's own arrow-key scrolling all come from the platform — there is no gesture code here, and no new dependency.

label is required: the root is a labelled region, so a screen reader user can find the whole carousel and skip past it in one move. Each slide is announced as "slide, 3 of 5" unless you give it a better name.

More than one at a time

Set --sve-carousel-slide-size on the root. The controls are disabled from the measured scroll extent rather than from the slide index, which matters here: with three slides visible the last index is reachable while there is still track left to scroll, and a control disabled then would be lying.

Vertical

The arrow glyphs and the snap axis follow orientation.

Why it does not loop

An infinite track makes "slide 3 of 5" meaningless — there is no third of five on a ring — and it removes the only honest position affordance a carousel has: a Previous or Next that goes flat at the boundary. A sighted user loses a scrollbar; everyone else loses the ability to know they have reached the end.

Position is also read from the scroll container on every scroll rather than remembered, because a swipe, a flick and the arrow keys are all drivers this component cannot intercept. A stored index would disagree with the screen after any of them, and the slide label would start lying.

Why it does not auto-rotate

Auto-rotation is only acceptable with all of: a visible pause control, stopping on hover, stopping on focus, and honouring prefers-reduced-motion. That chain is exactly what gets half-built, and content that moves on its own is one of the most documented accessibility failures on the web — it steals attention from the rest of the page and it moves the target out from under anyone who reads or clicks slowly.

So it is not built in. useCarousel() exposes next(), prev() and goTo(), so a consumer who genuinely needs rotation can drive it — and owns the pause button that has to come with it.

The controls above are the whole API. Rotation is yours to justify.

Props

Each part also takes its native element's attributes via prop spreading.

PropTypeDefault
orientation Orientation'horizontal'
label The carousel's accessible name, e.g. "Product photos". Required, and it is not decoration. The root is a labelled region so a screen reader user can find and skip the whole thing in one move; an unnamed region is an obstacle they have to read through to identify.string
labelledby Id of an existing visible heading, as an alternative to `label`.string
class Extra classes merged onto the root element.string
children required Snippet
PropTypeDefault
label Overrides the generated "N of M" name. Pass something meaningful when the slide has an identity of its own — a product name beats a position. The default exists because a slide with no name at all is announced as "group", which tells the user nothing about where they are.string
class Extra classes merged onto the slide.string
children required Snippet
PropTypeDefault
label Accessible name. Defaults to "Previous slide".string'Previous slide'
class Extra classes merged onto the button.string
children Snippet
PropTypeDefault
index required Zero-based index of the slide this dot scrolls to.number
label Accessible name. Defaults to "Go to slide N".string
class Extra classes merged onto the button.string