Carousel

Browse slides with native scrolling, snapping, and lightweight navigation controls

Theme
<div class="mx-auto w-full max-w-xl px-12">
    <sa-carousel aria-label="Voyager destinations">
        <sa-carousel-content>
            <sa-carousel-item aria-label="Kyoto">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/kyoto.jpg" alt="Kiyomizu-dera above red autumn foliage in Kyoto" width="1200" height="800" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Kyoto</p>
                        <p class="mt-1 text-sm text-white/90">Japan</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/oP50BOGpyj0" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Bhanu Singh</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Lisbon">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/lisbon.jpg" alt="Terracotta rooftops and historic churches in Lisbon" width="1200" height="800" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Lisbon</p>
                        <p class="mt-1 text-sm text-white/90">Portugal</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/alj8EUjeIj4" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">João Reguengos</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Cape Town">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/cape-town.jpg" alt="Waves breaking along the coast with Table Mountain beyond" width="1200" height="675" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Cape Town</p>
                        <p class="mt-1 text-sm text-white/90">South Africa</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/JiGoRzghR3E" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Matthys Pienaar</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Chiang Mai">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/chiang-mai.jpg" alt="The ancient brick chedi of Wat Chedi Luang in Chiang Mai" width="1200" height="675" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Chiang Mai</p>
                        <p class="mt-1 text-sm text-white/90">Thailand</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/QMGGhIkljKo" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">YingChu Chen</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
        </sa-carousel-content>
        <sa-carousel-previous/>
        <sa-carousel-next/>
    </sa-carousel>
</div>

Usage

Compose a carousel with <sa-carousel>, one <sa-carousel-content>, and its direct <sa-carousel-item> children. Add previous and next buttons as siblings of the content. Give the carousel a descriptive aria-label, or use aria-labelledby to reference a visible heading, and name each slide with aria-label or aria-labelledby.

<div class="px-12">
    <sa-carousel aria-label="Featured destinations">
        <sa-carousel-content>
            <sa-carousel-item aria-label="Kyoto">...</sa-carousel-item>
            <sa-carousel-item aria-label="Lisbon">...</sa-carousel-item>
        </sa-carousel-content>
        <sa-carousel-previous/>
        <sa-carousel-next/>
    </sa-carousel>
</div>

The buttons sit outside the viewport, so reserve space around the carousel, as in the wrapper above. Keep the viewport free of decorative borders and padding; put those on slide content or an outer wrapper. Set --carousel-spacing on the carousel to change the gap between slides.

CSS handles layout and scroll snapping. A small web component connects the buttons to the scroll container and updates their disabled state. Touch and trackpad scrolling remain native. When the viewport itself is focused, arrow keys follow the carousel's orientation and text direction; Home and End reach the first and last positions. Keys inside links, inputs, and other slide content retain their normal behavior. Navigation leaves keyboard focus on the control that was activated.

With both navigation buttons present and initialized, the native scrollbar is hidden. Without JavaScript, the content remains scrollable with its native scrollbar and the navigation controls are hidden. Controls require the browser's Invoker Commands API; browsers without it also retain the native scroller. The component does not depend on experimental CSS scroll buttons or scroll markers.

Examples

Responsive sizes

Items fill the viewport by default. Override their flex basis to show multiple items, accounting for the gap. This example shows two items from the medium breakpoint. Navigation advances to the next distinct item start, clamped at the end of the viewport; the final position may contain several items.

Theme
<div class="mx-auto w-full max-w-4xl px-12">
    <sa-carousel aria-label="Voyager destinations">
        <sa-carousel-content>
            <sa-carousel-item aria-label="Kyoto" class="md:basis-[calc(50%-0.5rem)]">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/kyoto.jpg" alt="Kiyomizu-dera above red autumn foliage in Kyoto" width="1200" height="800" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Kyoto</p>
                        <p class="mt-1 text-sm text-white/90">Japan</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/oP50BOGpyj0" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Bhanu Singh</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Lisbon" class="md:basis-[calc(50%-0.5rem)]">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/lisbon.jpg" alt="Terracotta rooftops and historic churches in Lisbon" width="1200" height="800" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Lisbon</p>
                        <p class="mt-1 text-sm text-white/90">Portugal</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/alj8EUjeIj4" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">João Reguengos</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Cape Town" class="md:basis-[calc(50%-0.5rem)]">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/cape-town.jpg" alt="Waves breaking along the coast with Table Mountain beyond" width="1200" height="675" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Cape Town</p>
                        <p class="mt-1 text-sm text-white/90">South Africa</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/JiGoRzghR3E" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Matthys Pienaar</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Chiang Mai" class="md:basis-[calc(50%-0.5rem)]">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/chiang-mai.jpg" alt="The ancient brick chedi of Wat Chedi Luang in Chiang Mai" width="1200" height="675" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Chiang Mai</p>
                        <p class="mt-1 text-sm text-white/90">Thailand</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/QMGGhIkljKo" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">YingChu Chen</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
        </sa-carousel-content>
        <sa-carousel-previous/>
        <sa-carousel-next/>
        <sa-carousel-indicators aria-label="Choose a destination"/>
    </sa-carousel>
</div>

Indicators

Add <sa-carousel-indicators> to generate one button for each distinct scroll position. Positions are recalculated when the viewport or item sizes change and when items are added or removed. The current position uses aria-current; these are navigation buttons, not tabs. Indicator names come from the corresponding slide's accessible label, falling back to its position number. Supply descriptive slide names and translate the indicator group's aria-label when localizing.

Theme
<div class="mx-auto w-full max-w-xl px-12">
    <sa-carousel aria-label="Voyager destinations">
        <sa-carousel-content>
            <sa-carousel-item aria-label="Kyoto">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/kyoto.jpg" alt="Kiyomizu-dera above red autumn foliage in Kyoto" width="1200" height="800" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Kyoto</p>
                        <p class="mt-1 text-sm text-white/90">Japan</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/oP50BOGpyj0" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Bhanu Singh</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Lisbon">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/lisbon.jpg" alt="Terracotta rooftops and historic churches in Lisbon" width="1200" height="800" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Lisbon</p>
                        <p class="mt-1 text-sm text-white/90">Portugal</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/alj8EUjeIj4" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">João Reguengos</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Cape Town">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/cape-town.jpg" alt="Waves breaking along the coast with Table Mountain beyond" width="1200" height="675" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Cape Town</p>
                        <p class="mt-1 text-sm text-white/90">South Africa</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/JiGoRzghR3E" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Matthys Pienaar</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Chiang Mai">
                <figure class="relative w-full aspect-[3/2] min-h-56 overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/chiang-mai.jpg" alt="The ancient brick chedi of Wat Chedi Luang in Chiang Mai" width="1200" height="675" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Chiang Mai</p>
                        <p class="mt-1 text-sm text-white/90">Thailand</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/QMGGhIkljKo" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">YingChu Chen</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
        </sa-carousel-content>
        <sa-carousel-previous/>
        <sa-carousel-next/>
        <sa-carousel-indicators aria-label="Choose a destination"/>
    </sa-carousel>
</div>

All slide content remains available to assistive technology and keyboard navigation. Off-screen links and buttons scroll into view when focused. Smooth button navigation respects the reduced-motion preference. There is no automatic rotation or live announcement of every scroll movement.

Vertical

Set orientation="CarouselOrientation.Vertical", give the content a definite height, and reserve space above and below for the navigation buttons.

Theme
<div class="mx-auto w-full max-w-xl px-12 py-12">
    <sa-carousel aria-label="Voyager destinations" orientation="CarouselOrientation.Vertical">
        <sa-carousel-content class="h-72">
            <sa-carousel-item aria-label="Kyoto">
                <figure class="relative h-full overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/kyoto.jpg" alt="Kiyomizu-dera above red autumn foliage in Kyoto" width="1200" height="800" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Kyoto</p>
                        <p class="mt-1 text-sm text-white/90">Japan</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/oP50BOGpyj0" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Bhanu Singh</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Lisbon">
                <figure class="relative h-full overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/lisbon.jpg" alt="Terracotta rooftops and historic churches in Lisbon" width="1200" height="800" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Lisbon</p>
                        <p class="mt-1 text-sm text-white/90">Portugal</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/alj8EUjeIj4" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">João Reguengos</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Cape Town">
                <figure class="relative h-full overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/cape-town.jpg" alt="Waves breaking along the coast with Table Mountain beyond" width="1200" height="675" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Cape Town</p>
                        <p class="mt-1 text-sm text-white/90">South Africa</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/JiGoRzghR3E" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Matthys Pienaar</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
            <sa-carousel-item aria-label="Chiang Mai">
                <figure class="relative h-full overflow-hidden rounded-xl bg-muted">
                    <img src="/cities/chiang-mai.jpg" alt="The ancient brick chedi of Wat Chedi Luang in Chiang Mai" width="1200" height="675" class="absolute inset-0 h-full w-full object-cover" loading="lazy"/>
                    <figcaption class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/90 via-black/60 to-transparent px-5 pt-16 pb-5 text-white">
                        <p class="text-2xl font-semibold tracking-tight">Chiang Mai</p>
                        <p class="mt-1 text-sm text-white/90">Thailand</p>
                        <p class="mt-3 text-xs text-white/90">Photo by <a href="https://unsplash.com/photos/QMGGhIkljKo" target="_blank" rel="noopener noreferrer" class="underline underline-offset-2 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">YingChu Chen</a> on Unsplash</p>
                    </figcaption>
                </figure>
            </sa-carousel-item>
        </sa-carousel-content>
        <sa-carousel-previous/>
        <sa-carousel-next/>
    </sa-carousel>
</div>

This carousel uses native scrolling rather than Embla. Infinite looping, autoplay, mouse grab-and-drag, plugins, and Embla's programmatic API are not supported.

API Reference

Renders a <sel-carousel> element and forwards global attributes. A unique ID is generated unless you supply one. The default role is region; use role="group" when the carousel should not be a landmark. The default aria-roledescription is carousel.

Prop

Type

Renders a focusable <div> containing the slides. Its tabindex defaults to 0. Supply a height for vertical carousels. This is the single scroll viewport in a carousel.

Renders a <div> with role="group" and aria-roledescription="slide". Use aria-label or aria-labelledby to name it. Items must be direct children of the content and can contain arbitrary HTML or Tag Helpers.

Renders a <button> targeting the enclosing carousel. Defaults to a left chevron and the accessible label “Previous slide”. Set aria-label to translate the label or supply child content to replace the icon. The type defaults to button.

Prop

Type

Renders a <button> targeting the enclosing carousel. Defaults to a right chevron and the accessible label “Next slide”. Supports the same customization as the previous button.

Prop

Type

Renders an empty <div role="group"> that the component fills with position buttons. Do not supply child content. The default aria-label is “Choose a slide”. Use the .sa-carousel-indicator class and [aria-current="true"] selector to customize the generated buttons.

On this page