Velocity Scene Slider
A scene-card slider whose release velocity becomes the next card's spring velocity, with a stretching pill indicator.
This is a WebberUI Pro component
The install command below needs a licence to run; without one it returns 401.
A horizontal track of scene cards: each card holds any content, and you switch by dragging sideways or tapping the pill indicator. The interesting part is the release — the drag velocity is handed straight to the page-change spring as its initial velocity, so a fast flick lands fast and a slow push glides over, one continuous velocity curve from finger to rest. While dragging, the pills stretch and shrink with progress, and cards sharpen as they reach the centre. Good for feature tours, onboarding and product walkthroughs.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add "https://webberui.com/r/velocity-scene-slider.en.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<VelocitySceneSlider/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add "https://webberui.com/r/velocity-scene-slider.en.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/velocity-scene-slider.
Usage
import {
VelocitySceneSlider,
type VelocityScene,
} from "@/components/ui/velocity-scene-slider";
const scenes: VelocityScene[] = [
{ id: "inbox", label: "Inbox triage", content: <InboxScene /> },
{ id: "schedule", label: "Scheduling", content: <ScheduleScene /> },
{ id: "board", label: "Team board", content: <BoardScene /> },
];
export function Tour() {
const [index, setIndex] = React.useState(0);
return (
<VelocitySceneSlider
scenes={scenes}
index={index}
onIndexChange={setIndex}
onRelease={({ velocity }) => console.log(`${Math.round(velocity)} px/s`)}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
scenes | VelocityScene[] | — | Scene cards; 3–7 works best |
index | number | — | Current scene index in controlled mode; managed internally when omitted |
defaultIndex | number | 0 | Initial scene index in uncontrolled mode |
onIndexChange | (index: number) => void | — | Called when the scene changes (drag, indicator and keyboard all report) |
onRelease | (info: VelocitySceneRelease) => void | — | Called on drag release with the velocity, offset and target index |
stiffness | number | 260 | Page-change spring stiffness; higher settles faster |
damping | number | 30 | Page-change spring damping; lower overshoots more |
velocityThreshold | number | 400 | Release velocity (px/s) above which the page changes regardless of distance |
loop | boolean | false | Wrap around to the other end at the first/last scene |
showIndicator | boolean | true | Show the pill indicator below the track |
ariaLabel | string | "Scene carousel" | Accessible label for the whole carousel |
className | string | — | Passed through to the root element |
VelocityScene
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique scene id |
label | string | — | Scene name, used by the indicator buttons and screen readers |
content | React.ReactNode | — | Card content |
VelocitySceneRelease
| Field | Type | Default | Description |
|---|---|---|---|
velocity | number | — | Horizontal velocity at release (px/s, negative = leftward) |
offset | number | — | Horizontal offset travelled during the drag (px) |
index | number | — | Scene index the slider heads to after release |
CSS variables
| Variable | Default | Description |
|---|---|---|
--wb-velocity-scene-indicator-duration | 240ms | Transition time for pill width and colour |
--wb-velocity-scene-gap | 24px | Gap between scene cards; the step is measured from layout, so dragging and positioning follow any override |
How it works
- Velocity matching: on release the drag velocity is passed unchanged as the page-change spring's
velocity. With the samestiffness/damping, a flick and a slow push therefore arrive at different speeds and feel different; indicator and keyboard navigation start from velocity 0 - Page-change rule: velocity wins — if the absolute release velocity exceeds
velocityThreshold, the slider moves one page in that direction; otherwise the drag must pass 25% of the card width, or it springs back - Wrapping: with
loopon, flicking past the last card returns to the first. The track then travels far in the opposite direction, and carrying the release velocity would shoot it the wrong way first, so wrapping starts from velocity 0 - Indicator: at rest the active pill is 28px and the others 8px, with a CSS transition; while dragging, the two neighbouring widths are interpolated every frame from continuous progress, so the pill looks stretched
- Scene cards: scale 0.94→1 and opacity 0.6→1 interpolate from the distance to centre; cards are 82% of the container width so neighbours peek in on both sides
- Controlled mode: if the parent does not accept the new index from
onIndexChange, the track springs back to the original scene
Accessibility
- The root is
role="region"witharia-roledescription="carousel", focusable with Tab and showing afocus-visiblering - Each card is
role="group"witharia-roledescription="slide"and anaria-labelof "Scene n of N: name"; off-centre cards areinert, so they are neither focusable nor announced - Once focused, Left/Right arrows change page and Home/End jump to the first/last scene
- The indicator is a
role="tablist"; each pill is atype="button"tab witharia-selectedandaria-controls, using a roving tabindex so focus follows arrow-key moves - The current scene is announced through
aria-live="polite" - With the system "reduce motion" setting on, the post-release spring momentum is disabled and page changes become a 150ms total fade out/in; the pills jump straight to their target width without a transition
Radial Burst Gallery
Images burst out from the centre into a ring that slowly orbits; selecting one flies it to the centre while the rest shrink back onto the ring, with keyboard navigation.
Card Stack Scroll
A scroll effect where cards stick to the top one after another, stacking up and shrinking away layer by layer.