Guides
Drag & momentum
What a gesture does by default, what dragFree changes and how the animation speed is spent.
What a gesture does
Pointer and drag listeners are attached to the viewport element directly, so nothing has to be spread onto your markup. A press that moves less than a few pixels is left alone and reaches whatever is inside the slide — a link stays clickable, a button stays pressable. Past that threshold the gesture becomes a drag and the track follows the pointer.
The axis is locked on the way in: a gesture that starts more vertical than horizontal is released back to the page, so a swipe down the screen scrolls it instead of nudging the carousel sideways — the other way round on a vertical carousel. Mouse, touch and pen all go through the same pointer events, so there is no separate touch mode to enable, and only the primary button starts a drag.
dragFree
By default one gesture moves by one slide at most, however far it was dragged — the carousel snaps to the neighbouring position when you let go. With dragFree the momentum of the drag is kept instead: the track coasts and rests wherever it stops, with no snap at the end.
// one gesture moves by one slide at most
const [carouselRef] = useSwipiCarousel()
// the track coasts and rests wherever it stops
const [freeRef] = useSwipiCarousel({ dragFree: true })- Snapping is the right default for slides you page through — a hero, a gallery, anything with dots.
dragFreesuits dense rows of thumbnails or tags, where landing between two items is not a mistake.- Either way
selectedIndexkeeps reporting the nearest snap, so dots and arrows go on working.
The CSS a gesture needs
Two declarations make dragging feel right, and both are yours to write.
.carousel__viewport {
overflow: hidden;
touch-action: pan-y;
}
.carousel__track {
cursor: grab;
user-select: none;
}
.carousel__track:active {
cursor: grabbing;
}touch-action: pan-yon the viewport lets a vertical swipe scroll the page while a horizontal one drags the carousel. Without it the browser claims the gesture first.user-select: noneon the track stops a drag from selecting the text inside the slides.
How the movement is animated
Moves the carousel makes on its own — an arrow, a dot, scrollTo, an autoplay tick — run for animationSpeed milliseconds on an ease-out curve. A flick is different: its duration comes from how fast you were moving, so a quick gesture lands quickly and a lazy one glides.
const [carouselRef] = useSwipiCarousel({
animationSpeed: 700 // ms, default 300
})animationSpeed: 0 removes the movement entirely, which is what a fade-in carousel wants — the slides cross-fade in CSS while the track jumps. The same jump is what respectReducedMotion produces when the system asks for less motion.
