# Inertia > Declarative animation primitives for React Native, built on react-native-reanimated. Inspired by Framer Motion (web) and react-spring (cross-platform). DX-first — animations are props on a component, not imperative shared values, worklets, and `useAnimatedStyle` boilerplate. ## Install ```sh pnpm add @rootnative/inertia react-native-reanimated ``` Enable the Reanimated Babel plugin per its install guide. ## Imports ```ts import { Motion, Presence, MotionConfig, useGesture, useVariants, useMotionValue, useAnimation, useSpring, useBooleanSpring, useTransform, useShadow, useColorTransition, useScroll, } from '@rootnative/inertia' // Opt-in MD3 / iOS state-layer overlay helper: import { useGestureLayer } from '@rootnative/inertia/gesture-layer' // or for tree-shaking the primitives: import { MotionView } from '@rootnative/inertia/view' import { MotionText } from '@rootnative/inertia/text' import { MotionImage } from '@rootnative/inertia/image' import { MotionPressable } from '@rootnative/inertia/pressable' import { MotionScrollView } from '@rootnative/inertia/scroll-view' ``` ## Public API - `Motion.View` / `Motion.Text` / `Motion.Image` / `Motion.Pressable` / `Motion.ScrollView` — animatable primitives. Per-primitive style inference (no shared `ViewStyle & TextStyle & ImageStyle` fallback). - `` — mount / unmount transitions; children need explicit `key`s. Exiting children get `pointerEvents: 'none'` automatically. - `` — gates motion against the OS reduce-motion setting (default `"user"`) and registers named transitions for the subtree. A registered name is accepted anywhere a `TransitionConfig` is: the `transition` prop (top-level and per-property/per-layer), the `layout` prop, and the value-layer hooks (`transition="selection"`, `useBooleanSpring(checked, 'selection')`). Nested providers merge (child overrides per name); unknown names warn in dev and fall back to the default spring. Names are consumer vocabulary — no presets ship with the library. Optional compile-time narrowing via `declare module '@rootnative/inertia' { interface RegisteredTransitions { ... } }`. Custom components join the registry with `useNamedTransitions()` + `resolveNamedTransition(input, registry)`. - `useGesture(transition?)` — hook-form of the `gesture` prop. Returns 0↔1 progress shared values (`pressed`, `focused`, `focusVisible`, `hovered`) plus a `handlers` bag to spread on a `Pressable`. Use when one gesture needs to drive multiple animated siblings (focus rings, MD3 state-layer halos, multi-element compositions). - `useGestureLayer(states, options?)` — opt-in helper at `@rootnative/inertia/gesture-layer` for the **strongest-active-layer-wins** composition (MD3 state-layer haloes, iOS-translucent overlays). Supply per-state target maps (`rest` / `hovered` / `focused` / `focusVisible` / `pressed` / `disabled`); the hook owns the worklet. Numeric keys compose via clamped-max with `rest` as the floor; color keys compose via priority cascade with `interpolateColor`; `disabled` (JS-side flag) overrides every gesture layer. Returns `{ style, handlers, states }` — `states` is the per-state 0↔1 progress shared values (`hovered` / `focused` / `focusVisible` / `pressed` / `disabled`) behind the composed style, for deriving extra styles (e.g. `useShadow({ progress: states.hovered })`) from the same gesture wiring. Reach for plain `useGesture` for additive blends or per-key custom rules. - `useVariants(variants, initial?)` — returns `{ current, transitionTo }` controller for the `controller` prop. - `useMotionValue(initial)` — thin pass-through over `useSharedValue`. Returns a `SharedValue` directly so it interops with every Reanimated API without unwrapping. - `useAnimation(target, transition?)` — drive a `SharedValue` toward `target` with any `TransitionConfig` (spring / timing / decay / no-animation, plus `repeat`) or a registered `TransitionName`. The general-purpose value-layer hook for boolean-state progress, indeterminate loops, and anywhere the same value needs to feed multiple `useAnimatedStyle` blocks. - `useSpring(target, config?)` — spring-only shorthand. Animates a `SharedValue` toward `target` with react-spring vocab (or a spring-typed registered `TransitionName`; non-spring names warn and fall back to the default spring). `target` may be a plain number (effect-driven) or a `SharedValue` (UI-thread reaction); the latter is the gesture-smoothing path. - `useBooleanSpring(active, config?)` — sugar over `useSpring` for the recurring "spring 0↔1 progress from a boolean" shape (checkbox checks, accordion expansions, drawer open/closed). Identical mechanics to `useSpring(active ? 1 : 0, config)` — the named form so the call site reads as the boolean it represents. - `useTransform(value, inputRange, outputRange, options?)` / `useTransform(transformer)` — interpolate a numeric shared value onto a number or color range, or derive any value from any number of shared values via a worklet. Non-worklet transformers are auto-wrapped. - `useShadow({ from, to, progress })` — pure value-layer interpolator between two `ShadowConfig`s (`shadowOpacity` / `shadowRadius` / `shadowOffset` / `elevation` / `shadowColor`) driven by a `SharedValue` (0→1). Returns an animated style fragment to spread onto `style`; only emits keys present on either side, absent sides default to natural zero. The hook does not animate on its own — drive `progress` with a spring, a scroll-derived `useTransform`, or any other shared value source. - `useColorTransition(progress, [from, to], options?)` — pure value-layer interpolator for a single color channel, driven by a `SharedValue` (0→1). Returns an animated style fragment with one color key (default `backgroundColor`; configurable via `options.key` to `color` / `borderColor` / `tintColor` / `shadowColor` / per-side border colors). For raw `SharedValue` output, use `useTransform(progress, [0, 1], [from, to])` instead. - `useScroll()` — returns `{ scrollX, scrollY, onScroll }` for use with `Motion.ScrollView`. Scroll events fire on the UI thread. - `createMotionComponent(C)` — wrap any component with the same Motion prop surface, inferring style from `C`. - `buildReleaseAnimation(transition, toValue)` — worklet-safe single-step animation builder (spring / timing / decay / no-animation). For assigning Inertia-resolved animations to shared values from inside a gesture worklet. Used internally by `@rootnative/inertia-gestures`'s `useDrag({ onRelease })`. - `resolveTransition(config, toValue, callback?)` — JS-thread resolver: bakes a `TransitionConfig` + target into a Reanimated animation (`withSpring` / `withTiming` / `withDecay`, plus `delay` / `repeat` wrapping) ready to assign to a shared value. The building block for custom animated components that accept Inertia's `transition` shape; this is what the gradient and SVG adapter packages use. - `resolveAnimatableValue(value, base, factory?)` — one level up: resolves a full per-property `animate` value (plain target, `{ to, ...override }` step, or keyframe array → `withSequence` with sequence-level `repeat`) into one animation. Gives custom components the complete `animate` grammar. - `cubicBezier(x1, y1, x2, y2)` / `cubicBezier(css)` — builds a `timing.easing` value from cubic-bezier control points: four numbers, a W3C CSS `cubic-bezier(0.2, 0, 0, 1)` string (design-token form), or a CSS keyword (`'linear'` | `'ease'` | `'ease-in'` | `'ease-out'` | `'ease-in-out'`). Makes CSS-format easing tokens directly consumable — pair with named transitions to feed a theme's motion tokens into the registry. Invalid input throws (`x1`/`x2` must be within [0, 1]; `step-*` and `linear(...)` unsupported). - `ensureWorkletEasing(easing)` — normalizes any accepted easing input (plain function, worklet, or Reanimated 4 `EasingFunctionFactory`) into a worklet `withTiming` can consume. Only needed for manual `withTiming` calls — the resolvers and primitives apply it automatically. ## Motion props `initial` (mount-only, non-reactive after first render — pass `false` to skip), `animate`, `exit`, `variants`, `controller`, `gesture`, `transition`, `layout`, `onAnimationEnd`. `layout` accepts `true` (default spring) or a `TransitionConfig` (spring / timing) and animates position + size changes that come from outside the `animate` flow (siblings reordering, dimensions toggling). `'decay'` downgrades to spring; `'no-animation'` and reduced motion both skip the animation. ## Transitions | `type` | Public config | Default? | | ---------------- | ---------------------------------------------------------------- | -------- | | `'spring'` | `tension`, `friction`, `mass`, `velocity`, `delay`, `repeat` | yes | | `'timing'` | `duration`, `easing` (`EasingFunction` or Reanimated 4 `EasingFunctionFactory` — `Easing.bezier(...)` works directly, no `.factory()` call needed), `delay`, `repeat` | | | `'decay'` | `velocity`, `deceleration`, `clamp`, `delay` | | | `'no-animation'` | — | | Spring uses react-spring vocabulary (`tension`, `friction`, `mass`). Reanimated's raw `stiffness` / `damping` never appear in the public API. Per-property `transition` entries override the top-level config. ## Sequences and repeat ```tsx ``` `repeat`: `number` (finite, alternating) | `'infinite'` | `{ count, alternate }`. ## Gestures ```tsx ``` Sub-states composite as a **priority cascade** in the order `hovered → focused → focusVisible → pressed` (highest-priority active layer's target wins — values are never summed). Each declared sub-state owns an independent progress (0↔1) that fades in/out with its own transition; the worklet composites layers via `lerp` (numerics) / `interpolateColor` (colors). MD3 release-while-hovered behaves correctly — the press layer fades out without disturbing the hover layer. Per-layer transitions via `transition.`: ```tsx transition={{ pressed: { type: 'timing', duration: 50 }, hovered: { type: 'timing', duration: 90 }, }} ``` `focusVisible` engages only on keyboard focus (W3C `:focus-visible`) so click-focus on web doesn't flash a ring; on native it tracks `focused`. `hovered` is a no-op on native. ## Caveats - Every `Motion.*` primitive **throws in dev** when `style` is a function. Reanimated's animated-component wrapper would silently drop the function-form `style={({ pressed }) => ...}` that RN's `Pressable` accepts, so the throw catches the footgun at the source. Drive press/focus/hover styling through `gesture` instead, or compute conditional styles once in render. Production builds skip the check (the styles would still be dropped — catching it in dev is the safety net). - `initial` is read once on mount and intentionally non-reactive. To reset after a state change, change the component `key`, remount via ``, or drive the value through a controller. Pass `initial={false}` to skip the initial-mount animation. ## Animatable properties (alpha) Numeric: `opacity`, `translateX`, `translateY`, `scale`, `scaleX`, `scaleY`, `rotate`, `rotateX`, `rotateY`, `width`, `height`, `borderRadius`, `shadowOpacity`, `shadowRadius`, `elevation`. Rotation values are degrees; the factory wraps them as `'${value}deg'`. `rotateX` / `rotateY` need a sibling `perspective` style entry to render in 3D. Color: `backgroundColor`, `borderColor`, `color`, `tintColor` (Image only), `shadowColor`. Hex, `rgb()` / `rgba()`, `hsl()` / `hsla()`, and named colors all work; the target string is forwarded straight through `withSpring` / `withTiming` and Reanimated handles RGBA interpolation natively. Nested object: `shadowOffset: { width, height }`. The only nested-object style on the surface — internally decomposes into two synthetic axis SVs that the worklet recomposes into a single `shadowOffset` prop. v0.1 supports the **single-value form only** (`{ width: 0, height: 4 }`); sequences / array keyframes / per-axis transition splits are out of scope, drop to `useMotionValue` for those. Auto-layout transitions ship via the `layout` prop (`true` / `TransitionConfig`) on every `Motion.*` primitive — see Layout. Shared element transitions ship via the `layoutId` prop: pair the same id on a source and target `Motion.*` and Inertia FLIPs between them on mount (parent-relative coords, rect-only animation, 1s TTL). ## Optional adapter packages - `@rootnative/inertia-gradients` — `MotionLinearGradient` over `expo-linear-gradient`. Animatable: `colors`, `start`, `end`, `locations`. - `@rootnative/inertia-svg` — animatable SVG primitives over `react-native-svg`, all under the `MotionSvg` namespace (`MotionSvg.Path` / `.Circle` / `.Rect` / `.Line`) or as direct imports. `MotionPath` animates `d` (path morphing on structurally-compatible paths — same command sequence after implicit-repeat expansion; remount with `key` to switch shape), `fill`, `stroke`, `strokeWidth`, opacities, `strokeDashoffset`. `MotionCircle` / `MotionRect` / `MotionLine` animate their geometry props, paints, and `strokeDasharray` (element-wise; array length locked at mount). `createMotionSvgComponent(Component, { animatableProps, colorProps, arrayProps })` — the factory behind the prebuilt shapes — wraps any other `react-native-svg` element with the same `initial` / `animate` / `transition` surface; `transition` accepts named transitions from the nearest `` (top-level and per-property). - `@rootnative/inertia-gestures` — `useDrag`, `useSwipe`, `usePan` over `react-native-gesture-handler`. `useDrag` accepts an `onRelease` worklet that returns per-axis Inertia release transitions (snap-to-tick spring, decay with bounds, etc.) — the release velocity stays on the UI thread, no JS round-trip. ## Docs - Full docs: https://rootnative.github.io/inertia/ - Per-page LLM reference: https://rootnative.github.io/inertia/llms-full.txt - Source: https://github.com/rootnative/inertia