Carousel
Headless carousel built on CSS scroll-snap with multi-slide display and partial-slide peeking.
Usage
The Carousel provides slide navigation with native drag/swipe via CSS scroll-snap. Slides register with a step context for programmatic navigation.
Anatomy
<script setup lang="ts">
import { Carousel } from '@vuetify/v0'
</script>
<template>
<Carousel.Root>
<Carousel.Viewport>
<Carousel.Item />
</Carousel.Viewport>
<Carousel.Indicator />
<Carousel.Previous />
<Carousel.Next />
<Carousel.Progress />
<Carousel.LiveRegion />
</Carousel.Root>
</template>Examples
Indicator
Dot indicators show which slide is active and allow direct navigation. The Carousel.Indicator exposes an items array via slot props — render each dot with v-bind="item.attrs" for built-in keyboard navigation and ARIA.
Multi-Slide Display
Show multiple slides at once with the per-view prop. This is useful for card grids, skill lists, or product carousels where users can browse items in groups.
Peek
Add padding to the viewport to reveal a portion of adjacent slides, signaling to the user that more content is available and encouraging drag/swipe interaction.
Recipes
Circular Navigation
Enable wrapping from last slide to first with the circular prop.
<template>
<Carousel.Root circular>
<Carousel.Viewport>
<Carousel.Item v-for="i in 5" :key="i" :value="i">
Slide {{ i }}
</Carousel.Item>
</Carousel.Viewport>
</Carousel.Root>
</template>Vertical Orientation
Set orientation="vertical" for a vertically scrolling carousel.
<template>
<Carousel.Root orientation="vertical">
<Carousel.Viewport style="height: 300px">
<Carousel.Item v-for="i in 5" :key="i" :value="i">
Slide {{ i }}
</Carousel.Item>
</Carousel.Viewport>
</Carousel.Root>
</template>Disabled State
Disable all navigation via the disabled prop on the root.
<template>
<Carousel.Root disabled>
<Carousel.Viewport>
<Carousel.Item v-for="i in 3" :key="i" :value="i">
Slide {{ i }}
</Carousel.Item>
</Carousel.Viewport>
<Carousel.Previous>Previous</Carousel.Previous>
<Carousel.Next>Next</Carousel.Next>
</Carousel.Root>
</template>Instant Scroll
Set behavior="instant" to disable smooth scrolling on programmatic navigation.
<template>
<Carousel.Root behavior="instant">
<Carousel.Viewport>
<Carousel.Item v-for="i in 5" :key="i" :value="i">
Slide {{ i }}
</Carousel.Item>
</Carousel.Viewport>
</Carousel.Root>
</template>Styling with Data Attributes
All component state is exposed via data-* attributes for CSS-only styling:
/* Style the selected slide */
[data-selected] { opacity: 1; }
/* Dim inactive slides */
[aria-hidden="true"] { opacity: 0.4; }
/* Hide nav buttons at boundaries */
[data-edge] { visibility: hidden; }
/* Drag interaction */
[data-dragging] { cursor: grabbing; user-select: none; }Accessibility
The Carousel implements the WAI-ARIA Carousel Pattern↗︎.
| Element | Role / Attribute |
|---|---|
| Root | role="region", aria-roledescription="carousel", aria-label, aria-disabled |
| Viewport | aria-live="polite" |
| Slide | role="group", aria-roledescription="slide", aria-label="N of M" |
| Previous | aria-label via locale, aria-controls links to viewport |
| Next | aria-label via locale, aria-controls links to viewport |
| Indicator | role="tablist" container with aria-orientation, role="tab" per dot, aria-selected |
| Progress | role="progressbar", aria-valuenow, aria-valuemin, aria-valuemax |
| LiveRegion | role="status", aria-live="polite", aria-atomic |
Slides outside the visible window are marked with aria-hidden="true" so screen readers only announce visible content. The LiveRegion provides a dedicated announcement channel for slide changes, using a 100ms delay for reliable screen reader detection.
The per-view prop controls how many slides are visible at once. The Viewport measures its own layout (padding, gaps) from the DOM and computes each slide’s width automatically. It uses CSS scroll-snap so the browser handles all scroll physics and snap behavior natively.
The Carousel is built on CSS scroll-snap for native drag/swipe support. For fade transitions, use the Step provider component with Presence for mount/unmount animations — it gives you the same navigation model (next/prev/circular) without scroll-based layout.
Use the autoplay prop with an interval in milliseconds. The root slot exposes isAutoplay, isPaused, remaining, play, stop, pause, and resume for controlling playback. Autoplay pauses automatically during touch and mouse drag interactions. Use Carousel.Progress to visualize the timer.
<template>
<Carousel.Root v-slot="{ isAutoplay, play, stop }" :autoplay="5000">
<Carousel.Viewport>
<!-- slides -->
</Carousel.Viewport>
<Carousel.Progress class="h-1 bg-surface-variant" />
</Carousel.Root>
</template>