Skip to content
Native Animation
Vertical Range Window animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add vertical-range-window

Vertical Range Window

A vertical range control with independent handles and a draggable interval band, with configurable bounds, palette, and fonts.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

Related guides

A vertical range control with independent handles and a draggable interval band, with configurable bounds, palette, and fonts.

How it works

  1. Normalize the domain and initial interval to min, max, step, minSpan, and maxSpan. Keep both endpoints together in one shared range value.
  2. A handle drag changes its endpoint; a band drag moves the entire interval while preserving its span. Interaction state identifies the active minimum, maximum, or window.
  3. Send quantized provisional values through onRangeChange and onRangeRequest. On release, normalize the final interval and emit one onRangeCommit; controlled range updates remain authoritative.
  4. Apply the same clamping and quantization to ref, keyboard, and accessibility actions. Animate the linked band and readout from the shared state, using final positions instead of springs with reduced motion.

Vertical range windows work for temperature, price, time, equalizer bands, and lighting. The host owns validation and persistence through the request and commit callbacks.

Install

Copy VerticalRangeWindow.tsx and index.ts into your project. Install dependencies with npx expo install expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-worklets. The component uses expo-linear-gradient, react-native-gesture-handler, react-native-reanimated, and react-native-worklets, already present in the host app. No images or additional assets are required.

Usage

<VerticalRangeWindow
  min={32}
  max={104}
  step={1}
  defaultRange={{ min: 52, max: 78 }}
  formatValue={(value) => `${value}°F`}
  onRangeChange={(next, meta) => preview(next, meta.reason)}
  onRangeCommit={(next) => save(next)}
  accessibilityLabel="Ambient temperature range"
/>

Props

PropTypeDefaultDescription
rangeRangeValueundefinedControlled { min, max } interval.
defaultRangeRangeValue34%–67% of the domainInitial uncontrolled interval, clamped and quantized.
min, maxnumberRequiredDomain bounds.
stepnumber1Quantization step; invalid or nonpositive values use 1.
minSpannumber0Minimum interval span.
maxSpannumberInfinityMaximum span; nonfinite values resolve to the domain span.
onRangeChange(next: RangeValue, meta: RangeMeta) => voidundefinedProvisional and final changes.
onRangeCommit(next: RangeValue, meta: RangeMeta) => voidundefinedOne final change per release, keyboard/accessibility adjustment, or reset.
onRangeRequest(next: RangeValue, meta: RangeMeta) => voidundefinedReceives the same proposals as onRangeChange for host validation.
formatRange(value: RangeValue) => stringundefinedLarge readout; fallback joins formatted endpoints with an en dash.
formatValue(value: number, edge: RangeEdge) => stringString(value)Formats each endpoint.
labelsPartial<RangeLabels>Built-in English labelsMinimum, Maximum, Window, disabled, error, instructions, and reset copy.
palettePartial<RangePalette>Paper/Night paletteSemantic overrides, including rangeCool and rangeWarm for the gradient.
motionRangeMotionundefinedreduce: true enables reduced motion in addition to the system preference.
colorScheme'light' | 'dark' | 'auto'undefined (Paper)Only 'auto' follows the system; 'dark' selects Night.
showDistributionbooleanundefined (off)Vary tick lengths with distribution values.
distributionreadonly number[]Built-in 12-value sequenceOptional tick distribution.
fontFamily, titleFontFamily, monoFontFamilystringundefinedHost-loaded font families; omitted values use platform fonts. titleFontFamily is declared but is not rendered by the current implementation.
disabledbooleanundefined (enabled)Disable interaction.
errorstringundefinedDisplay an error and disable interaction.
styleStyleProp<ViewStyle>undefinedRoot styling.
testIDstringundefinedRoot identifier; child controls use vertical-range-window as the fallback prefix.
accessibilityLabelstringlabels.window ('Selected range')Root accessibility label.

RangeMeta contains reason, active, and requestId. The ref exposes setRange(next, active?), focusEdge, focusWindow, reset, and cancel. setRange defaults active to 'window', animates to the normalized interval, and emits change/request callbacks without a commit. reset commits the normalized initial interval. Values are clamped and quantized before callbacks.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1 with Worklets 0.13.0, Gesture Handler 3.3, and Expo Linear Gradient 58.0.3. Supports iOS, Android, and web with Expo Go-compatible native dependencies. The host must provide GestureHandlerRootView. The component adapts to its measured container and applies no safe-area padding.

Accessibility

The minimum handle, maximum handle, and selected window expose adjustable controls. Handle labels combine labels.min or labels.max with the formatted value; the window label includes both values. The root uses accessibilityLabel or labels.window. Increment/decrement actions move by one step; Escape cancels. Handles provide at least 48 dp targets, controls expose disabled state when disabled or showing an error, and errors use a polite live region. The range text redundantly communicates the current interval. Reduced Motion applies final positions without springs and fades the changing readout.