Swipeable, physics-driven carousel built on react-voodoo
react-voodoo-slider is a carousel/slider component built entirely on react-voodoo axes. A single Voodoo axis (slideAxis) drives the whole carousel. Each slide occupies a fixed range of units on that axis, enabling:
- Infinite looping via ghost slides
- Physics-based drag with momentum and snap-to-slide
- Per-slide inner tweeners with
entering/leaving/visible/visibleNextaxes - Auto-scroll with automatic hover pause
- Controlled and uncontrolled active index
- Hot-swappable style presets — swap layouts at runtime
- Optional prev/next buttons included in each preset
npm install react-voodoo-sliderreact-voodoo is a peer dependency and must be installed alongside this package. React 16, 17, and 18 are all supported.
import Slider from "react-voodoo-slider";
// 1. Plain children
const SimpleSlider = () => (
<Slider style={{ height: 400 }}>
<div>Slide A</div>
<div>Slide B</div>
<div>Slide C</div>
</Slider>
);
// 2. Data array + renderItem
const items = [
{ label: "Slide 1", backgroundImage: "https://picsum.photos/400/700?random=1" },
{ label: "Slide 2", backgroundImage: "https://picsum.photos/400/700?random=2" },
{ label: "Slide 3", backgroundImage: "https://picsum.photos/400/700?random=3" },
];
const DataSlider = () => (
<Slider
items={items}
renderItem={(item, index, ref) => (
<div ref={ref} style={{ backgroundImage: `url(${item.backgroundImage})` }}>
{item.label}
</div>
)}
/>
);| Prop | Type | Default | Description |
|---|---|---|---|
defaultIndex |
number |
0 |
Initial active index (uncontrolled). |
index |
number |
— | Controlled active index. |
defaultStyleId |
string |
"default" |
Key of the style preset to apply. |
items |
array |
— | Data array — used together with renderItem. |
renderItem |
function |
— | (item, index, refCb) => ReactElement — renders one slide from data. |
children |
ReactNode |
— | Plain React children used as slides when items/renderItem are not provided. |
infinite |
boolean |
false |
Enable infinite looping via ghost slides. |
autoScroll |
number |
— | Auto-advance interval in ms. Pauses automatically on hover. |
onChange |
function |
— | (index, item) => void — fires when a transition completes. |
onWillChange |
function |
— | (index, item) => void — fires immediately when the target index is known (predictive). |
onClick |
function |
— | (event, realIndex, api) => void — slide click handler. |
updateItemAxes |
boolean |
false |
Drive per-slide inner tweener axes (entering, leaving, visible, visibleNext). |
hookUpdateItemAxes |
function |
— | (itemTweener, relPos) => void — extra hook called per-slide per-frame when updateItemAxes is enabled. |
visibleItems |
number |
preset | How many slides are simultaneously visible. Controls drag sensitivity. |
overlaps |
number |
1/visibleItems |
Fraction of a slide visible from the neighbouring slot. |
maxJump |
number |
— | Maximum waypoints inertia can skip in a single flick. |
dragHook |
function |
— | (delta) => delta — transform the raw drag delta before it is applied. |
defaultInitial |
object |
preset | Base (t=0) style for every slide node (overrides preset). |
defaultEntering |
array |
preset | Tween descriptors for a slide entering the centre (overrides preset). |
defaultLeaving |
array |
preset | Tween descriptors for a slide leaving the centre (overrides preset). |
carouselStyle |
object |
preset | Style for the outermost container. |
wrapperStyle |
object |
preset | Style for the inner slide strip. |
prevBtnStyle |
object |
preset | Style for the ‹ button. Omit to hide. |
nextBtnStyle |
object |
preset | Style for the › button. Omit to hide. |
autoScrollEaseFn |
string |
"easeQuadInOut" |
Easing function for programmatic transitions. |
autoScrollEaseDuration |
number |
750 |
Duration in ms for programmatic transitions. |
style |
object |
{} |
Extra styles merged into the carousel container. |
className |
string |
"" |
Extra class names on the root element. |
The api object is available as the third argument of the onClick callback. It exposes the following methods:
| Method | Description |
|---|---|
api.goTo(index) |
Jump to a specific index (wraps in infinite mode). |
api.goNext() |
Advance one slide forward. |
api.goPrev() |
Go one slide backward. |
api.updateItemsAxes(pos) |
Force-sync per-slide inner axes from a raw slideAxis position. |
<Slider
onClick={(event, index, api) => {
if (someCondition) api.goTo(0);
}}
>
...
</Slider>A preset is a plain object whose keys map to the same-named props on the slider. You can register custom presets via the exported customStyles map:
import Slider, { customStyles } from "react-voodoo-slider";
import myPreset from "./myPreset";
customStyles.myPreset = myPreset;<Slider defaultStyleId="myPreset" />| Key | Description |
|---|---|
visibleItems |
Number of slides visible at once. |
carouselStyle |
Outermost container styles. |
wrapperStyle |
Inner strip styles. |
prevBtnStyle / nextBtnStyle |
Button styles (omit either key to hide that button). |
defaultInitial |
Base style applied to each slide node at t=0. |
defaultEntering |
Tween descriptors for a slide approaching the centre. |
defaultLeaving |
Tween descriptors for a slide departing from the centre. |
dragHook |
Optional (delta) => delta transform applied to raw drag input. |
| Preset id | Description |
|---|---|
"default" |
Full-width, 1-up. Slides pass over each other horizontally. |
"ThreeItems" |
3-up side-by-side strip with flex layout and nav buttons. |
"FourItems" |
5-up strip — 4 full slides plus two half-visible slides peeking from the edges. |
"slider3d" |
5-up 3D fan arc using perspective and multi-layer transforms. |
When updateItemAxes={true}, each child slide is expected to be a react-voodoo sub-tweener that exposes named axes. The slider drives these axes every frame based on each slide's position relative to the centre:
| Axis id | Range | Meaning |
|---|---|---|
entering |
0 → 100 |
Slide approaching centre from the right. |
leaving |
0 → 100 |
Slide departing from centre to the left. |
visible |
0 → 100 |
Continuous proximity to centre (100 = fully centred). |
visibleNext |
0 → 100 |
Binary: 100 inside the visible neighbourhood, 0 far away. |
import Voodoo from "react-voodoo";
const MySlide = ({ record, voodooRef }) => {
const [tweener, ViewBox] = Voodoo.hook();
// Expose the tweener so the parent slider can drive its axes
voodooRef?.(tweener);
return (
<ViewBox>
<Voodoo.Axis id="entering" defaultPosition={0} />
<Voodoo.Axis id="leaving" defaultPosition={100} />
<Voodoo.Axis id="visible" defaultPosition={0} />
<Voodoo.Axis id="visibleNext" defaultPosition={0} />
<Voodoo.Node
style={{ opacity: 0, transform: [{ translateX: "3em" }] }}
axes={{
visible: [{
from: 0, duration: 100,
apply: { opacity: 1, transform: [{ translateX: "-3em" }] }
}]
}}
>
<div>{record.label}</div>
</Voodoo.Node>
</ViewBox>
);
};
// Usage:
<Slider updateItemAxes={true} infinite={true}>
{items.map((rec, i) => <MySlide record={rec} key={i} />)}
</Slider>The internal slideAxis position is calculated as follows:
axisPos = 100 + dec + slideIndex × step
│ │ │
│ │ └─ step = 100 × overlaps
│ └─ ghost-slide offset (infinite mode only)
└─ base offset (room for the leaving tween of slide 0)
Each slide's tween descriptors are the shared entering + leaving curve, time-shifted to slideIndex × step. So slide 0 animates during [0, 200], slide 1 during [step, step + 200], and so on.