Skip to main content
WireKit
Copy for LLM

Carousel

The <x-wirekit::carousel> component creates a slide-based content rotator with autoplay, loop navigation, and indicator dots. It follows the WAI-ARIA Carousel pattern and announces slide changes to screen readers via a live region.

Usage

Basic Carousel
<x-wirekit::carousel>
    <x-wirekit::carousel.slide>
        <div class="p-12 text-center">
            <h3>Slide 1</h3>
            <p>Welcome to the carousel.</p>
        </div>
    </x-wirekit::carousel.slide>

    <x-wirekit::carousel.slide>
        <div class="p-12 text-center">
            <h3>Slide 2</h3>
            <p>Second slide content.</p>
        </div>
    </x-wirekit::carousel.slide>

    <x-wirekit::carousel.slide>
        <div class="p-12 text-center">
            <h3>Slide 3</h3>
            <p>Third slide content.</p>
        </div>
    </x-wirekit::carousel.slide>
</x-wirekit::carousel>

With Autoplay

Enable automatic slide advancement. The carousel pauses on hover and focus, then resumes on mouse leave and blur.

Autoplay Carousel

Without Loop

When loop is disabled, the Previous button is disabled on the first slide and the Next button is disabled on the last slide.

No-Loop Carousel

Slides can contain any content — images, cards, or complex layouts:

Image Carousel

With real images in your project:

<x-wirekit::carousel :autoplay="true" :interval="4000" aria-label="Product photos">
    <x-wirekit::carousel.slide>
        <img src="/images/product-1.jpg" alt="Product front view" class="w-full h-64 object-cover" />
    </x-wirekit::carousel.slide>
    <x-wirekit::carousel.slide>
        <img src="/images/product-2.jpg" alt="Product side view" class="w-full h-64 object-cover" />
    </x-wirekit::carousel.slide>
</x-wirekit::carousel>

Vertical Orientation

Set orientation="vertical" to scroll the slides up and down instead of left/right. The carousel pins a default viewport height (override it with a style="height: …" or a height class); each slide fills that viewport automatically, and the previous/next buttons move to the top and bottom edges.

Vertical carousel

Width & Layout

The carousel fills its parent width. Constrain it with Tailwind classes directly:

<x-wirekit::carousel class="max-w-2xl mx-auto">
    <x-wirekit::carousel.slide>…</x-wirekit::carousel.slide>
</x-wirekit::carousel>

Slide height is determined by content. For fixed-height slides, apply height classes to the slide content.

Multiple slides per view

per-view shows more than one slide at a time — a product row, a logo rail, a gallery strip. The browser scrolls and snaps; nothing is translated.

Three slides per view

per-view takes 14. Each step keeps a narrower layout on small screens — three-up on a 375px phone is three unreadable slivers — and shows part of the next slide, which is what tells the reader there is a next slide.

It only applies horizontally. A vertical carousel is one slide tall by construction, so there is no second slot to share.

label is what a screen reader reads first, and it makes the carousel a landmark someone can jump straight to:

<x-wirekit::carousel label="Customer stories">…</x-wirekit::carousel>

Name it for what it holds. "Carousel" is the default and says nothing.

The carousel provides three navigation methods:

  • Previous / Next buttons — circular buttons positioned at the left and right edges
  • Indicator dots — clickable dot indicators at the bottom center
  • Autoplay — automatic slide advancement with configurable interval

Autoplay Behavior

autoplay rotates the slides on a timer. When you turn it on, the carousel grows a play/pause button — because anything that moves on its own for more than five seconds needs a way to stop it (WCAG 2.2.2), and pausing on hover is not that way: a touch user cannot hover, and neither can a keyboard user driving a screen reader.

<x-wirekit::carousel autoplay :interval="4000" label="Highlights">…</x-wirekit::carousel>

Hovering or focusing the carousel still pauses the rotation — but that is separate from the button. A mouse passing over the carousel does not make the button claim you paused it.

Reduced motion is handled for you Under prefers-reduced-motion: reduce the carousel stays still until asked: the auto-advance holds, and moving between slides jumps rather than glides. The play button remains available, so a reader who wants the rotation can start it.

Keyboard

Key Action
Tab Move focus to navigation buttons and indicator dots
Enter / Space Activate the focused button or indicator

Behavior

  • CSS translate — slides move via translateX() with a 500ms ease-in-out transition
  • AutoplaysetInterval timer managed by Alpine; cleaned up in destroy()
  • Slide counting — total slides are counted from [data-wk-carousel-slide] elements in the DOM
  • Livewire navigate — autoplay timer is cleared on destroy() to prevent leaks during SPA navigation

Props

<x-wirekit::carousel>

Prop Type Default Description
autoplay bool false Automatically advance slides
interval int 5000 Autoplay interval in milliseconds
loop bool true Wrap around from last slide to first (and vice versa)
orientation string 'horizontal' 'horizontal' (left/right) or 'vertical' (up/down; pins a default viewport height)
perView int 1 Slides visible at once — 14 (horizontal only; an unknown value degrades to 1)
label string 'Carousel' Accessible name for the carousel scroll region
scope string|null null Scoped personalization key

<x-wirekit::carousel.slide>

Prop Type Default Description
scope string|null null Scoped personalization key

Sub-Components

Component Purpose
carousel.slide Individual content panel within the carousel

Accessibility

This is the non-tabbed carousel pattern: a named region containing a scrollable list of labeled groups. It is deliberately not a tablist — a tablist means exactly one selected panel, and per-view shows several slides at once, so there would be no single panel for a tab to point at.

  • Container: role="region", aria-roledescription="carousel", aria-label from the label prop
  • Scroll region: tabindex="0" with its own label, so the track is reachable and scrollable by keyboard
  • Slides: role="group", aria-roledescription="slide" — each slide is a labeled group, not a tab panel
  • Indicators: a role="group" picker of buttons; aria-current marks the slide leading the view
  • Nav buttons: aria-label="Previous slide" / aria-label="Next slide"
  • Chevron SVGs: aria-hidden="true" (decorative)
  • Live region: aria-live="polite" announces "Slide X of Y" on every slide change
  • Disabled buttons: native disabled attribute when loop is off and at first/last slide

Override the default label when the carousel content is not images — for example, label="Testimonials" or label="Feature highlights". It is the first thing a screen reader reads.

Indicator size on touch

The indicator dots are 8×8 and sit close together, which is under the 24×24 minimum of WCAG 2.5.8 Target Size (Minimum) and too close for its spacing exception. They rely on that criterion's equivalent control exception instead: the previous/next buttons are 40×40, are rendered unconditionally, and reach every slide the dots reach.

That means the dots are a shortcut, never the only route. If you build your own indicator row, either keep the built-in navigation buttons or size your indicators to 24×24 with 24px between their centers — do not ship the dots alone at this size.

Keyboard Interaction

Key Action
Tab Move focus into the carousel
ArrowLeft Move to the previous slide
ArrowRight Move to the next slide
Home Jump to the first slide
End Jump to the last slide
Enter / Space Activate the focused navigation button or indicator dot

Pitfalls

  • Don't use a carousel for primary content. WCAG research consistently finds <1% of users interact past the first slide. For must-see content use a hero with all items inline.
  • Don't auto-rotate without a pause control. WCAG 2.2.2 — anything moving longer than 5s needs a user-controllable pause.

Design Tokens

Token Used for
--font-wk-sans Caption font family
--color-wk-text Caption text
--color-wk-accent Active indicator dot
--color-wk-bg-elevated Nav-button background
--color-wk-bg-subtle Inactive indicator dot
--color-wk-border Nav-button border
--color-wk-ring Focus ring (nav buttons + indicators)
--ring-wk-width Focus ring width
--border-wk-width Border width
--radius-wk-lg Slide + nav-button border radius
--shadow-wk-md Nav-button drop shadow
--padding-wk-x-sm / --padding-wk-y-sm Nav-button padding
--opacity-wk-disabled Disabled indicator visual weight
--transition-wk-duration Slide transition

Personalization

Override classes globally via WireKit::personalize():

use Pushery\WireKit\WireKit;

WireKit::personalize('carousel', [
    'base' => 'relative overflow-hidden rounded-none',
]);

WireKit::personalize('carousel.slide', [
    'base' => 'w-full shrink-0 p-8',
]);

Further Reading

Was this page helpful?

Voting requires cookies or local storage. What we store