Skip to content
Native Animation
StepperThermostatOdometerControlSmart home

Rolling Temperature Stepper

A tall thermostat control that rolls only the digits that change and washes the card with warm or cool color as the setting moves away from neutral.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI.

npx nativeanimation add rolling-temperature-stepper

About this animation

A tall thermostat control that rolls only the digits that change and washes the card with warm or cool color as the setting moves away from neutral.

How it works

  1. onLayout supplies the card dimensions, and every radius, control zone, type size, and animation distance is derived from those measurements.
  2. The formatted number is split into right-aligned character windows. Each window owns one shared progress value and keeps an outgoing and incoming character, so unchanged digits remain completely still.
  3. When a second update interrupts a roll, the current incoming character becomes the next outgoing character. Its new progress starts below zero when necessary, preserving its on-screen position before it continues toward the next value.
  4. Two offset ghost copies follow each moving character. Their opacity and vertical stretch peak halfway through the roll to suggest motion blur without a native blur effect.
  5. A shared value from −1 to 1 drives the cool-to-warm tint, while its absolute value controls tint strength. Press and limit feedback remain independent from the displayed value.

Install

npx expo install expo-linear-gradient react-native-reanimated

Usage

import RollingTemperatureStepper from './RollingTemperatureStepper';

export function ThermostatCard() {
  return <RollingTemperatureStepper style={{ width: 143, height: 218 }} />;
}

Use a controlled value, visual knobs, and the imperative API when integrating it into a larger climate screen:

import { useRef, useState } from 'react';
import RollingTemperatureStepper, {
  type RollingTemperatureStepperRef,
} from './RollingTemperatureStepper';

export function ThermostatCard() {
  const [temperature, setTemperature] = useState(70);
  const stepperRef = useRef<RollingTemperatureStepperRef>(null);

  return (
    <RollingTemperatureStepper
      ref={stepperRef}
      value={temperature}
      min={55}
      max={82}
      neutral={68}
      tintRange={8}
      warmColor="#E9A7AF"
      coolColor="#9FC3EE"
      fontFamily="YourSansFont"
      onChange={setTemperature}
      style={{ width: 160, height: 244 }}
    />
  );
}

The forwarded ref exposes increment(), decrement(), and setValue(value). In controlled mode these methods request a value through onChange; the visible number follows the value prop.

Props

PropTypeDefaultDescription
valuenumber—Controlled temperature.
defaultValuenumber70Initial temperature when uncontrolled.
minnumber60Minimum allowed temperature.
maxnumber85Maximum allowed temperature.
stepnumber1Amount changed by each press or repeat.
neutralnumber70Temperature that has no warm or cool tint.
tintRangenumber3Distance from neutral at which the tint reaches full strength.
unitstring'°'Static unit displayed after the rolling number.
warmColorstring'#E9A7AF'Tint used above neutral.
coolColorstring'#9FC3EE'Tint used below neutral.
cardColorstring'#FFFFFF'Base card color.
textColorstring'#0B0B0F'Number and plus/minus color.
repeatOnHoldbooleantrueRepeats after a 450 ms hold, then every 110 ms.
fontFamilystringSystem fontNumber font family.
onChange(value: number) => void—Called with a requested, clamped value.
incrementAccessibilityLabelstring'Increase temperature'Screen-reader label for the upper control.
decrementAccessibilityLabelstring'Decrease temperature'Screen-reader label for the lower control.
styleStyleProp<ViewStyle>—Outer-card style.
testIDstring—Test identifier applied to the outer card.

Compatibility

  • Expo SDK 57
  • React Native 0.86
  • Reanimated 4.5
  • iOS: supported, not yet verified on a device
  • Android: supported, not yet verified on a device
  • Web: verified by the project gate
  • Works in Expo Go because its native dependencies are bundled with the SDK

Accessibility

Both halves are labeled buttons, with overridable labels for product-specific wording. Reduced Motion replaces digit travel with a 120 ms crossfade, removes the ghost trail and card scale, and keeps value, tint, press feedback, and limit behavior functional.