Motion values
Description
A motion value is a numeric channel you write to imperatively. Subscribers
(typically a styled primitive like Box) read the latest value and commit it
to the DOM on web or to the active motion driver's animated primitive on
native — without scheduling a React render.
Two consumer hooks: useMotionValue creates one, useTransform derives one
from another. Both return a MotionValue<T> you bind to a style prop on
Box. The runtime detects motion-value-typed props and wires the
subscription internally.
useMotionValue
useMotionValue
stablefunction useMotionValue<T extends string | number>(initial: T): MotionValue<T>
Starting value. Read on first mount only — subsequent renders with a different argument do not reset the value. Drive the value externally via `.set()`.
Returns a stable MotionValue<T> scoped to the calling component's lifetime.
The reference identity is preserved across renders, so passing it as a prop
does not churn child renders.
useTransform
useTransform has two forms. The range form maps a numeric source through a
piecewise-linear interpolation. The function form maps a source through an
arbitrary pure transformer.
Range form
useTransform
stablefunction useTransform<O extends string | number>( source: MotionValue<number>, inputRange: readonly number[], outputRange: readonly O[], options?: UseTransformOptions, ): MotionValue<O>
Numeric source value to transform.
Sorted-ascending list of source breakpoints.
Corresponding output values at each breakpoint. Numbers interpolate linearly; recognised colour strings interpolate in the configured colour space; unit-matched length strings (`8px → 16px`) strip the unit, lerp, re-append.
Optional configuration — currently `{ colorSpace?: "srgb" | "oklab" | "oklch" }` for colour output ranges.
Numbers interpolate piecewise-linearly across inputRange. Strings classify
at hook setup into one of three paths:
- Colour — hex,
rgb(),rgba(),hsl(),hsla(),oklab(),oklch(), and the CSS named colours all parse. Interpolation runs in the configuredcolorSpace('srgb'by default;'oklab'/'oklch'for perceptually-uniform hue rotations). - Unit-matched — same-suffix length strings (
'8px' ↔ '16px','1rem' ↔ '2rem','25% ↔ '75%') strip the unit, lerp, re-append. - Step — mixed or unrecognised shapes return the segment's starting value (the v1 fallback).
$… token strings resolve against the active theme at hook setup, so
theme-aware colour ranges work directly:
Inputs outside inputRange clamp to the nearest edge value.
Function form
useTransform
stablefunction useTransform<S extends string | number, O extends string | number>( source: MotionValue<S>, transformer: (value: S) => O, ): MotionValue<O>
Source motion value of any string or number type.
Pure mapping function. Runs on every source change. Closes over current props via the closure; the latest transformer reference takes effect on the next source change.
The function form runs the transformer on every source change and pushes the
result into the derived motion value. Use it when the mapping is not a simple
range lerp — non-linear easing, conditional logic, multiple sources composed
via successive useTransform calls.
The derived value subscribes to the source on mount and unsubscribes on unmount. Re-rendering with a different transformer fn picks up the new mapping without re-subscribing.
MotionValue
The runtime interface every motion value implements.
.set() short-circuits with Object.is when the new value matches the
current — a redundant set to the same value is a no-op. .on('change', cb)
returns the unsubscribe; subscribers fire synchronously inside .set().
createMotionValue
createMotionValue
stablefunction createMotionValue<T extends string | number>(initial: T): MotionValue<T>
Starting value.
Allocate a motion value outside React. Useful for global motion values,
non-React consumers, or testing. Inside components, prefer useMotionValue
— it owns the lifetime cleanly.
isMotionValue
isMotionValue
stablefunction isMotionValue(value: unknown): value is MotionValue<string | number>
Brand check. Returns true for any value carrying the motionValueBrand
symbol. Useful when writing helpers that accept both literal values and
motion values.
Style props that accept motion values
Motion values bind to a fixed list of style-prop slots on Box in v1. Pass
one in place of the literal value:
- Opacity, layout, size:
opacity,width/w,height/h,minWidth/minW,minHeight/minH,maxWidth/maxW,maxHeight/maxH,top,right,bottom,left,start,end,borderRadius,fontSize,zIndex. - Transform: the full
transformprop, plus the shorthand axesx,y,z,rotate,rotateX,rotateY,rotateZ,scale,scaleX,scaleY,skew,skewX,skewY. Multiple axis motion values on the sameBoxshare thetransformslot and recompose on every change.
Other props (background colour, padding, gap, etc.) take literal values
only in v1. Drive them through a useTransform-derived numeric and a
known-shape prop instead.
Embedding a motion value inside a responsive object —
<Box opacity={{ base: mv, md: 1 }}> — is rejected at the type level.
Per-breakpoint motion values are out of scope for v1; consumers wanting that
shape combine useTransform with a breakpoint signal in the transformer.
Behavioural contracts
Two non-obvious rules that make motion values worth their weight.
Updates bypass transition
Motion-value writes commit imperatively. On web that's a direct write to
element.style; on native it routes through the active driver's animated
primitive. Neither path consults the element's CSS transition value, so
easing on .set() is not applied by the motion-value runtime.
When you want a tween-on-set, drive the motion value through useSpring (or
a transformer that interpolates against a separate progress signal).
Updates do not trigger a React re-render
.set() notifies subscribers synchronously. None of those subscribers call
into React's render cycle — the styled primitive applies the value directly
to its element. A page that updates a hundred motion values per frame still
renders zero React components per frame.
Consumers reading the value via .get() in a render body get a stale
snapshot; the value channel is meant to bypass React, not feed it. If you
need to render conditionally on the value, branch in a subscriber or move
the render-driving state into useState.
Cross-platform notes
Both renderers accept the same motion-value surface. Two differences worth naming:
- Native is numeric-only on the default driver. String motion values
(e.g.
'#ff0000') warn at runtime and skip — the JS-threadAnimated.Valuehas no string interpolation primitive. Numeric values pass through cleanly. ThereanimatedDriver's UI-thread integration writes worklet shared values; same numeric-only constraint applies in v1. - Reanimated driver runs MV updates on the UI thread. Register
reanimatedDriveronce at startup; motion-value-bound styles then update on the UI thread underuseAnimatedStyle, with the transform-axis composer running inside the worklet. JS-thread subscribers still observe changes via arunOnJSbridge.
Related
useSpring— spring physics driving a motion value via.set(target).- Animation — declarative
transition/enterStyle/exitStylemotion props. - Motion values — recipes — canonical use cases.