Skip to content
Destyler UI Destyler UI Destyler UI

Carousel

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

Features

Install

Install the component from your command line.

Terminal window
      
        
npm install @destyler/carousel @destyler/vue
Terminal window
      
        
npm install @destyler/carousel @destyler/react
Terminal window
      
        
npm install @destyler/carousel @destyler/svelte
Terminal window
      
        
npm install @destyler/carousel @destyler/solid

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import * as carousel from '@destyler/carousel'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
const [state, send] = useMachine(
carousel.machine({
id: useId(),
}))
const api = computed(() =>
carousel.connect(state.value, send, normalizeProps),
)
</script>
<template>
<div v-bind="api.getRootProps()">
<div v-bind="api.getItemGroupProps()">
<div v-bind="api.getItemProps({ index:0 })">
<img>
</div>
</div>
<div v-bind="api.getControlProps()">
<button v-bind="api.getPrevTriggerProps()"/>
<div v-bind="api.getIndicatorGroupProps()">
<button v-bind="api.getIndicatorProps({ index: 0 })" />
</div>
<button v-bind="api.getNextTriggerProps()"/>
</div>
</div>
</template>
import * as carousel from '@destyler/carousel'
import { normalizeProps, useMachine } from '@destyler/react'
import { useId } from 'react'
export default function Carousel() {
const [state, send] = useMachine(
carousel.machine({
id: useId(),
})
)
const api = carousel.connect(state, send, normalizeProps)
return (
<>
<div {...api.getRootProps()}>
<div {...api.getItemGroupProps()}>
<div {...api.getItemProps({ index: 0 })}>
<img />
</div>
</div>
<div {...api.getControlProps()}>
<button {...api.getPrevTriggerProps()} />
<div {...api.getIndicatorGroupProps()}>
<button {...api.getIndicatorProps({ index: 0 })}/>
</div>
<button {...api.getNextTriggerProps()}/>
</div>
</div>
</>
)
}
<script lang="ts">
import * as carousel from '@destyler/carousel'
import { normalizeProps, useMachine } from '@destyler/svelte'
const uid = $props.id();
const [state, send] = useMachine(
carousel.machine({
id: uid,
}),
)
const api = $derived(carousel.connect(state, send, normalizeProps))
</script>
<div {...api.getRootProps()}>
<div {...api.getItemGroupProps()}>
<div {...api.getItemProps({ index: 0 })}>
<img />
</div>
</div>
<div {...api.getControlProps()}>
<button {...api.getPrevTriggerProps()}>
</button>
<div {...api.getIndicatorGroupProps()}>
<button {...api.getIndicatorProps({ index: 0 })}></button>
</div>
<button {...api.getNextTriggerProps()}></button>
</div>
</div>
import type { Component } from 'solid-js'
import * as carousel from '@destyler/carousel'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId } from 'solid-js'
const Carousel: Component = () => {
const [state, send] = useMachine(
carousel.machine({
id: createUniqueId(),
}))
const api = createMemo(() => carousel.connect(state, send, normalizeProps))
return (
<>
<div {...api().getRootProps()}>
<div {...api().getItemGroupProps()}>
<div {...api().getItemProps({ index: 0 })}>
<img />
</div>
</div>
<div {...api().getControlProps()}>
<button {...api().getPrevTriggerProps()} />
<div {...api().getIndicatorGroupProps()}>
<button {...api().getIndicatorProps({ index: 0 })}/>
</div>
<button {...api().getNextTriggerProps()} />
</div>
</div>
</>
)
}
export default Carousel

To create a vertical carousel, set the orientation property in the machine’s context to vertical.

const [state, send] = useMachine(
carousel.machine({
orientation: "vertical",
}),
)

Setting the initial slide

To set the initial slide of the carousel, pass the page property to the machine’s context.

The page 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 [state, send] = useMachine(
carousel.machine({
page: 2,
}),
)

Setting the number of slides to show at a time

To customize number of slides to show at a time, set the slidesPerPage property in the machine’s context. The value must be an integer.

const [state, send] = useMachine(
carousel.machine({
slidesPerPage: 2,
}),
)

Setting the number of slides to move at a time

To customize number of slides to move at a time, set the slidesPerMove property in the machine’s context. The value must be an integer or auto.

If the value is auto, the carousel will move the number of slides equal to the number of slides per page.

const [state, send] = useMachine(
carousel.machine({
slidesPerMove: 2,
}),
)

Ensure the slidesPerMove is less than or equal to the slidesPerPage to avoid skipping slides.

To allow looping of slides, set the loop property in the machine’s context to true.

const [state, send] = useMachine(
carousel.machine({
loop: true,
}),
)

Setting the gap between slides

To customize spacing between slides, set the spacing property in the machine’s context to a valid CSS unit.

const [state, send] = useMachine(
carousel.machine({
spacing: "20px",
}),
)

Listening for page changes

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

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

To allow dragging the carousel with the mouse, set the allowMouseDrag property in the machine’s context to true.

const [state, send] = useMachine(
carousel.machine({
allowMouseDrag: true,
}),
)

To allow the carousel to autoplay, set the autoplay property in the machine’s context to true.

const [state, send] = useMachine(
carousel.machine({
autoplay: true,
}),
)

Alternatively, you can configure the autoplay interval by setting the delay property in the machine’s context.

const [state, send] = useMachine(
carousel.machine({
autoplay: { delay: 2000 },
}),
)

Styling guide

Earlier, we mentioned that each carousel part has a data-part attribute added to them to select and style them in the DOM.

[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

Machine Context

The carousel machine exposes the following context properties:

ids
Partial<{ 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.
translations
IntlTranslations
The localized messages to use.
slidesPerPage(default: 1)
number
The number of slides to show at a time.
slidesPerMove(default: "auto")
number | "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.
autoplay(default: false)
boolean | { delay: number; }
Whether to scroll automatically. The default delay is 4000ms.
allowMouseDrag(default: false)
boolean
Whether to allow scrolling via dragging with mouse
loop(default: false)
boolean
Whether the carousel should loop around.
page
number
The index of the active page.
spacing(default: "0px")
string
The amount of space between items.
padding
string
Defines the extra space added around the scrollable area, enabling nearby items to remain partially in view.
onPageChange
(details: PageChangeDetails) => void
Function called when the page changes.
inViewThreshold(default: 0.6)
number | number[]
The threshold for determining if an item is in view.
snapType(default: "mandatory")
"proximity" | "mandatory"
The snap type of the item.
slideCount
number
The total number of slides. Useful for SSR to render the initial ating the snap points.
onDragStatusChange
(details: DragStatusDetails) => void
Function called when the drag status changes.
onAutoplayStatusChange
(details: AutoplayStatusDetails) => void
Function called when the autoplay status changes.
dir(default: "ltr")
"ltr" | "rtl"
The document's text/writing direction.
id
string
The unique identifier of the machine.
getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
orientation(default: "horizontal")
Orientation
The orientation of the element.

Machine API

The carousel api exposes the following methods:

page
number
The current index of the carousel
pageSnapPoints
number[]
The current snap points of the carousel
isPlaying
boolean
Whether the carousel is auto playing
isDragging
boolean
Whether the carousel is being dragged. This only works when `draggable` is true.
canScrollNext
boolean
Whether the carousel is can scroll to the next view
canScrollPrev
boolean
Whether the carousel is can scroll to the previous view
scrollToIndex
(index: number, instant?: boolean) => void
Function to scroll to a specific item index
scrollTo
(page: number, instant?: boolean) => void
Function to scroll to a specific page
scrollNext
(instant?: boolean) => void
Function to scroll to the next page
scrollPrev
(instant?: boolean) => void
Function to scroll to the previous page
getProgress
() => number
Returns the current scroll progress as a percentage
play
() => void
Function to start/resume autoplay
pause
() => void
Function to pause autoplay
isInView
(index: number) => boolean
Whether the item is in view
refresh
() => void
Function to re-compute the snap points and clamp the page

Data Attributes

Root

attribute
description
data-scope
carousel
data-part
root
data-orientation
The orientation of the carousel

ItemGroup

attribute
description
data-scope
carousel
data-part
item-group
data-orientation
The orientation of the item
data-dragging
Present when in the dragging state

Item

attribute
description
data-scope
carousel
data-part
item
data-index
The index of the item
data-inview
Indicates whether the item is in view ("true" or "false")
data-orientation
The orientation of the item

Control

attribute
description
data-scope
carousel
data-part
control
data-orientation
The orientation of the control

PrevTrigger

attribute
description
data-scope
carousel
data-part
prev-trigger
data-orientation
The orientation of the prevtrigger

NextTrigger

attribute
description
data-scope
carousel
data-part
next-trigger
data-orientation
The orientation of the nexttrigger

IndicatorGroup

attribute
description
data-scope
carousel
data-part
indicator-group
data-orientation
The orientation of the indicatorgroup

Indicator

attribute
description
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

attribute
description
data-scope
carousel
data-part
autoplay-trigger
data-orientation
The orientation of the autoplaytrigger
data-pressed
Present when pressed