Skip to main content

A carousel component that leverages native CSS Scroll Snap for smooth, performant scrolling between slides.

Loading...

Features

  • Uses native CSS Scroll Snap
  • Supports horizontal and vertical orientations
  • Supports slide alignment (start, center, end)
  • Supports showing multiple slides at a time
  • Supports looping and auto-playing
  • Supports custom spacing between slides

Installation

Install the carousel package:

npm install @zag-js/carousel @zag-js/vue # or yarn add @zag-js/carousel @zag-js/vue

Anatomy

Check the carousel anatomy and part names.

Each part includes a data-part attribute to help identify them in the DOM.

Usage

Import the carousel package:

import * as carousel from "@zag-js/carousel"

The carousel package exports two key functions:

  • machine - State machine logic.
  • connect - Maps machine state to JSX props and event handlers.

Pass a unique id to useMachine so generated element ids stay predictable.

Then use the framework integration helpers:

Note: The carousel requires that you provide a slideCount property in the machine context. This is the total number of slides.

<script setup> import * as carousel from "@zag-js/carousel" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" const items = [ "https://tinyurl.com/5b6ka8jd", "https://tinyurl.com/7rmccdn5", "https://tinyurl.com/59jxz9uu", ] const service = useMachine(carousel.machine, { id: "1", slideCount: items.length, }) const api = computed(() => carousel.connect(service, normalizeProps)) </script> <template> <div v-bind="api.getRootProps()"> <div v-bind="api.getControlProps()"> <button v-bind="api.getPrevTriggerProps()">Prev</button> <button v-bind="api.getNextTriggerProps()">Next</button> </div> <div v-bind="api.getItemGroupProps()"> <div v-for="(image, index) in items" :key="index" v-bind="api.getItemProps({ index })" > <img :src="image" alt="" /> </div> </div> <div v-bind="api.getIndicatorGroupProps()"> <button v-for="(_, index) in api.pageSnapPoints" :key="index" v-bind="api.getIndicatorProps({ index })" ></button> </div> </div> </template>

Vertical orientation

Set orientation to vertical to render a vertical carousel.

const service = useMachine(carousel.machine, { orientation: "vertical", })

Setting the initial slide

Set defaultPage to define the initial page.

The defaultPage corresponds to the scroll snap position index based on the layout. It does not necessarily correspond to the index of the slide in the carousel.

const service = useMachine(carousel.machine, { defaultPage: 2, })

Controlling the current page

Use page and onPageChange for controlled navigation.

const service = useMachine(carousel.machine, { slideCount: 8, page, onPageChange(details) { setPage(details.page) }, })

Setting slides per page

Set slidesPerPage to control how many slides are visible per page.

const service = useMachine(carousel.machine, { slidesPerPage: 2, })

Setting slides per move

Set slidesPerMove to control how many slides advance on next/previous.

const service = useMachine(carousel.machine, { slidesPerMove: 2, })

Considerations

  • If the value is auto, the carousel will move the number of slides equal to the number of slides per page.
  • Ensure the slidesPerMove is less than or equal to the slidesPerPage to avoid skipping slides.
  • On touch devices, slidesPerMove is not enforced during active swiping. The browser's native scrolling and CSS Scroll Snap determine slide movement for optimal performance and UX.

Looping pages

Set loop to true to wrap around from last to first page.

const service = useMachine(carousel.machine, { loop: true, })

Setting the gap between slides

Set spacing to control the gap between slides.

const service = useMachine(carousel.machine, { spacing: "16px", })

Setting viewport padding

Set padding to keep neighboring slides partially visible.

const service = useMachine(carousel.machine, { padding: "16px", })

Variable-width slides

Set autoSize to true when slides have different widths.

const service = useMachine(carousel.machine, { autoSize: true, })

Listening for page changes

When the carousel page changes, the onPageChange callback is invoked.

const service = useMachine(carousel.machine, { onPageChange(details) { // details => { page: number } console.log("selected page:", details.page) }, })

Listening for drag and autoplay status

Use status callbacks to react to dragging and autoplay lifecycle changes.

const service = useMachine(carousel.machine, { onDragStatusChange(details) { console.log(details.type, details.isDragging) }, onAutoplayStatusChange(details) { console.log(details.type, details.isPlaying) }, })

Set allowMouseDrag to true to drag the carousel with a mouse.

const service = useMachine(carousel.machine, { allowMouseDrag: true, })

Set autoplay to true to start automatic slide movement.

const service = useMachine(carousel.machine, { autoplay: true, })

Alternatively, you can configure the autoplay interval by setting the delay property.

const service = useMachine(carousel.machine, { autoplay: { delay: 2000 }, })

Customizing accessibility messages

Use translations to customize localized trigger, item, and progress text.

const service = useMachine(carousel.machine, { slideCount: 5, translations: { nextTrigger: "Next slide", prevTrigger: "Previous slide", indicator: (index) => `Go to slide ${index + 1}`, item: (index, count) => `Slide ${index + 1} of ${count}`, autoplayStart: "Start autoplay", autoplayStop: "Stop autoplay", progressText: ({ page, totalPages }) => `Page ${page + 1} of ${totalPages}`, }, })

Styling guide

Each part includes a data-part attribute you can target in CSS.

[data-part="root"] { /* styles for the root part */ } [data-part="item-group"] { /* styles for the item-group part */ } [data-part="item"] { /* styles for the root part */ } [data-part="control"] { /* styles for the control part */ } [data-part="next-trigger"] { /* styles for the next-trigger part */ } [data-part="prev-trigger"] { /* styles for the prev-trigger part */ } [data-part="indicator-group"] { /* styles for the indicator-group part */ } [data-part="indicator"] { /* styles for the indicator part */ } [data-part="autoplay-trigger"] { /* styles for the autoplay-trigger part */ }

Active state

When a carousel's indicator is active, a data-current attribute is set on the indicator.

[data-part="indicator"][data-current] { /* styles for the indicator's active state */ }

Methods and Properties

The carousel's api exposes the following methods and properties:

Machine Context

The carousel machine exposes the following context properties:

  • idsPartial<{ root: string; item: (index: number) => string; itemGroup: string; nextTrigger: string; prevTrigger: string; indicatorGroup: string; indicator: (index: number) => string; }>The ids of the elements in the carousel. Useful for composition.
  • translationsIntlTranslationsThe localized messages to use.
  • slidesPerPagenumberThe number of slides to show at a time.
  • autoSizebooleanWhether to enable variable width slides.
  • slidesPerMovenumber | "auto"The number of slides to scroll at a time. When set to `auto`, the number of slides to scroll is determined by the `slidesPerPage` property.
  • autoplayboolean | { delay: number; }Whether to scroll automatically. The default delay is 4000ms.
  • allowMouseDragbooleanWhether to allow scrolling via dragging with mouse
  • loopbooleanWhether the carousel should loop around.
  • pagenumberThe controlled page of the carousel.
  • defaultPagenumberThe initial page to scroll to when rendered. Use when you don't need to control the page of the carousel.
  • spacingstringThe amount of space between items.
  • paddingstringDefines the extra space added around the scrollable area, enabling nearby items to remain partially in view.
  • onPageChange(details: PageChangeDetails) => voidFunction called when the page changes.
  • inViewThresholdnumber | number[]The threshold for determining if an item is in view.
  • snapType"proximity" | "mandatory"The snap type of the item.
  • slideCountnumberThe total number of slides. Useful for SSR to render the initial ating the snap points.
  • onDragStatusChange(details: DragStatusDetails) => voidFunction called when the drag status changes.
  • onAutoplayStatusChange(details: AutoplayStatusDetails) => voidFunction called when the autoplay status changes.
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
  • orientation"horizontal" | "vertical"The orientation of the element.

Machine API

The carousel api exposes the following methods:

  • pagenumberThe current index of the carousel
  • pageSnapPointsnumber[]The current snap points of the carousel
  • isPlayingbooleanWhether the carousel is auto playing
  • isDraggingbooleanWhether the carousel is being dragged. This only works when `draggable` is true.
  • canScrollNextbooleanWhether the carousel is can scroll to the next view
  • canScrollPrevbooleanWhether the carousel is can scroll to the previous view
  • scrollToIndex(index: number, instant?: boolean) => voidFunction to scroll to a specific item index
  • scrollTo(page: number, instant?: boolean) => voidFunction to scroll to a specific page
  • scrollNext(instant?: boolean) => voidFunction to scroll to the next page
  • scrollPrev(instant?: boolean) => voidFunction to scroll to the previous page
  • getProgress() => numberReturns the current scroll progress as a percentage
  • getProgressText() => stringReturns the progress text
  • playVoidFunctionFunction to start/resume autoplay
  • pauseVoidFunctionFunction to pause autoplay
  • isInView(index: number) => booleanWhether the item is in view
  • refreshVoidFunctionFunction to re-compute the snap points and clamp the page

Data Attributes

Root
data-scope
carousel
data-part
root
data-orientation
The orientation of the carousel
ItemGroup
data-scope
carousel
data-part
item-group
data-orientation
The orientation of the item
data-dragging
Present when in the dragging state
Item
data-scope
carousel
data-part
item
data-index
The index of the item
data-inview
Present when in viewport
data-orientation
The orientation of the item
Control
data-scope
carousel
data-part
control
data-orientation
The orientation of the control
PrevTrigger
data-scope
carousel
data-part
prev-trigger
data-orientation
The orientation of the prevtrigger
NextTrigger
data-scope
carousel
data-part
next-trigger
data-orientation
The orientation of the nexttrigger
IndicatorGroup
data-scope
carousel
data-part
indicator-group
data-orientation
The orientation of the indicatorgroup
Indicator
data-scope
carousel
data-part
indicator
data-orientation
The orientation of the indicator
data-index
The index of the item
data-readonly
Present when read-only
data-current
Present when current
AutoplayTrigger
data-scope
carousel
data-part
autoplay-trigger
data-orientation
The orientation of the autoplaytrigger
data-pressed
Present when pressed

CSS Variables

Root
--slides-per-page
The number of slides visible per page
--slide-spacing
The spacing between slides
--slide-item-size
The calculated size of each slide item
Edit this page on GitHub