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
onLayoutsupplies the card dimensions, and every radius, control zone, type size, and animation distance is derived from those measurements.- 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.
- 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.
- 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.
- 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-reanimatedUsage
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
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | Controlled temperature. |
defaultValue | number | 70 | Initial temperature when uncontrolled. |
min | number | 60 | Minimum allowed temperature. |
max | number | 85 | Maximum allowed temperature. |
step | number | 1 | Amount changed by each press or repeat. |
neutral | number | 70 | Temperature that has no warm or cool tint. |
tintRange | number | 3 | Distance from neutral at which the tint reaches full strength. |
unit | string | '°' | Static unit displayed after the rolling number. |
warmColor | string | '#E9A7AF' | Tint used above neutral. |
coolColor | string | '#9FC3EE' | Tint used below neutral. |
cardColor | string | '#FFFFFF' | Base card color. |
textColor | string | '#0B0B0F' | Number and plus/minus color. |
repeatOnHold | boolean | true | Repeats after a 450 ms hold, then every 110 ms. |
fontFamily | string | System font | Number font family. |
onChange | (value: number) => void | — | Called with a requested, clamped value. |
incrementAccessibilityLabel | string | 'Increase temperature' | Screen-reader label for the upper control. |
decrementAccessibilityLabel | string | 'Decrease temperature' | Screen-reader label for the lower control. |
style | StyleProp<ViewStyle> | — | Outer-card style. |
testID | string | — | 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.