About this animation
This React Native slider selects a numeric value through a vertical liquid vessel. One shared progress controls the fill, grip, and value position, while a damped response bends the meniscus against drag velocity. Releasing snaps to a step; track taps and separate adjustment buttons provide alternatives to dragging. The host controls the range, formatting, and accepted value, with callbacks for selected steps and successful commits. Use it for a savings target, intensity, or another value that benefits from a large liquid display. It uses Skia rather than a stock platform slider; read the published README for layout space and CanvasKit setup on web.
When to use it
- Set a savings target.
- Choose a bounded intensity.
- Display units beside a numeric input.
Related guides
A value slider with a motion-responsive liquid surface and rolling readout, with configurable bounds, liquid colors, and fonts.
How it works
- A single continuous shared progress value drives the fill, grip, and value position. React only receives selected steps for formatting and accessibility, never fluid animation frames.
- A UI-thread frame callback measures progress velocity. A damped fluid response (mass 1, stiffness 180, damping 27) turns that velocity into bounded cubic meniscus curvature.
- Skia clips the liquid gradient and surface light inside a rounded vessel. Highlights and two shadows give the vessel depth without opaque overlays on text.
- Dragging cancels the active progress spring. Release snaps to the nearest step with explicit mass and gesture velocity; the fluid settles even during a pause in the drag.
- Taps and adjustment buttons select a value without dragging. External value changes and
ref.setValue()use the same fluid response; they do not emit change callbacks.
Install
npx expo install @shopify/react-native-skia react-native-gesture-handler react-native-reanimated react-native-workletsWrap your app in GestureHandlerRootView. Configure Reanimated/Worklets for your Expo SDK. On web, load CanvasKit before mounting this component using Skia's supported web initialization. The catalog demo already does this.
Usage
import { LiquidValueSlider } from './liquid-value-slider';
<View style={{ height: 440 }}>
<LiquidValueSlider defaultValue={50} showTicks />
</View><LiquidValueSlider
min={0}
max={1000}
step={25}
defaultValue={200}
format={(value) => `$${value.toLocaleString('en-US')}`}
unitLabel="set aside each month"
colorScheme="dark"
colors={['#246448', '#A8F3C9']}
waveAmplitude={22}
accessibilityLabel="Monthly savings amount"
onChange={saveDraft}
onChangeEnd={commitSavings}
/>The component uses the host's available width (onLayout). It stretches vertically with flex: 1; use style={{ flexGrow: 0, flexShrink: 0, flexBasis: 'auto' }} for intrinsic height. The host owns safe areas and should provide scrolling for enlarged text or long custom formats. The value, vessel, scale, endpoint labels, and buttons occupy separate flow rows. The vessel adapts to width (144–380 points tall), or accepts vesselHeight (104–420 points). Reserve the vessel height plus about 220 points with a unit label and marks; allow scrolling when that exceeds the available space. The demo uses a scrollable, width-limited product screen for phones, tablets, landscape, and embedded containers.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
min, max, step | number | 0, 100, 1 | Numeric range and interval. Invalid ranges/steps receive safe defaults. |
value | number | — | External value; user changes are requested through callbacks. |
defaultValue | number | 50 | Initial uncontrolled value. |
onChange | (value: number) => void | — | Each changed step during a drag; requested target for taps/buttons. |
onChangeEnd | (value: number) => void | — | Once after a successful gesture or button/accessibility adjustment. Canceled drags do not commit. |
format | (value: number) => string | String | Large value and endpoints; may include currency or units. |
unitLabel | string | '' | Optional secondary line under the value. |
colors | [string, string] | ['#237B69', '#85F3C4'] | Liquid gradient and surface light. |
showTicks | boolean | false | Scale capped at 21 evenly spaced marks. Dense ranges use reference marks rather than every step. |
vesselHeight | number | Width-based | Vessel height in points, clamped to 104–420. |
waveAmplitude | number | 22 | Maximum dynamic curvature; bounded to 0–28 points. |
colorScheme | 'light' | 'dark' | 'auto' | 'auto' | Complete light/dark appearance. |
trackColor | string | Theme | Empty vessel. |
textColor, mutedColor | string | Theme | Main and secondary text. |
borderColor, controlColor | string | Theme | Rim/borders and adjustment surfaces. |
fontFamily, labelFontFamily | string | System | Value and supporting fonts. |
accessibilityLabel | string | 'Value' | Adjustable control name. |
decreaseLabel, increaseLabel | string | 'Decrease value', 'Increase value' | Button names. |
style | StyleProp<ViewStyle> | — | Container styling. |
testID | string | — | Root ID; -track, -decrease, and -increase suffixes identify controls. |
The forwarded LiquidValueSliderRef exposes setValue(value) (silent), increment(), and decrement() (user callbacks). Values are clamped and quantized. Endcaps reserve room for the grip at both range limits.
Compatibility
Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Gesture Handler 3.3, and Skia 2.14.0. Uses the installed Expo Go modules; no added native dependency. Web requires CanvasKit initialization.
Accessibility
The vessel exposes accessibilityRole="adjustable", numeric bounds, the formatted current value, and increment/decrement actions. Separate 48×48 buttons and track taps offer alternatives to dragging. Reduced Motion updates progress immediately, disables fluid deformation and value translation, and retains a gently curved static meniscus. Font scaling is enabled; values and endpoint labels can wrap.
FAQ
- Is this a horizontal slider?
- The published component uses a vertical liquid vessel. The slider guide builds a horizontal rail; choose the geometry that fits your screen.
- Can the host control the selected value?
- Yes. Supply value with onChange, or defaultValue for local state. min, max, step, format, and onChangeEnd configure the range and commit behavior.
- Can someone select a value without dragging?
- Yes. Track taps, separate adjustment buttons, and screen-reader increment/decrement actions provide alternatives. Reduced Motion disables fluid deformation and value travel.




