Skip to content

v1.1.0 — motion system

Choose a tag to compare

@0xNeit 0xNeit released this 27 May 04:12
· 40 commits to main since this release
db55bb3

The motion system. 25 issues across motion values, gestures, animations, and
the cross-platform primitives that consume them. Every package ships at 1.1.0.

Merged via #74.

Install

yarn add usemotif@1.1.0 @usemotif/tokens@1.1.0
import { Box, useMotionValue, useTransform, useDrag, useScroll, useSpring } from 'usemotif';

Added

Motion values

  • useMotionValue / useTransform / MotionValue. Imperative numeric
    channels that bypass React renders. Writes commit directly to the DOM on web
    or route through the active motion driver on native. The two non-obvious
    contracts that matter: .set() bypasses transition, and updates do not
    trigger re-renders. (#27)
  • useTransform real interpolation. Colors interpolate in sRGB by default;
    unit-matched length strings ('8px' ↔ '16px') strip the unit, lerp, re-append.
    Mixed shapes step at boundaries (the v1 fallback). (#36)
  • Perceptual color spaces. New { colorSpace: 'srgb' | 'oklab' | 'oklch' }
    option on useTransform. oklab and oklch keep saturated hue rotations
    vivid instead of muddying through grey; oklch interpolates hue along the
    shortest arc. Extended parsers handle hsl() / hsla(), oklab(), oklch(),
    and the 148 CSS named colors. (#53)
  • Theme-aware token outputs. useTransform resolves $colors.brand.red-style
    references against the active theme at hook setup; the resolved literals
    flow into the existing interpolation path. (#52)
  • Style-prop widening. opacity, sizing, position, borderRadius, fontSize,
    zIndex, transform — and the new transform shorthand axes (x, y, z,
    rotate*, scale*, skew*) — accept a MotionValue<number> directly on <Box>.

Transform shorthand props

  • x, y, z, rotate, scale, skew and axis variants on <Box>.
    Multiple shorthand props on the same element compose into one canonical-order
    transform declaration (translate → rotate → scale → skew). Web emits a CSS
    transform string; native emits an RN transform array. Motion values bound
    to multiple axes recompose per frame instead of clobbering each other.
    (#35)

Spring physics

  • useSpring. Returns a MotionValue<number> whose .set(target) springs
    from the current value over the spring's natural duration. Config takes a
    literal SpringConfig or a theme-token name ('$animations.bouncy').
    (#34)
  • Driver-routed spring on native. useSpring routes through
    Animated.spring on the default driver or withSpring on the Reanimated
    driver — the spring math runs off the JS thread when the driver supports it.
    Falls back to the JS-thread integrator for drivers that don't implement
    useSpringBacking. (#48)

Drag

  • useDrag. Pointer-event-driven on web, PanResponder on native. Returns
    x, y motion values for the drag offset plus a dragProps bag to spread
    on the target. Lifecycle callbacks (onDragStart, onDrag, onDragEnd)
    receive offset + velocity snapshots. (#25)
  • dragElastic, dragMomentum, dragTransition. Rubber-band overshoot
    past constraints; velocity-projected momentum + spring-settle on release;
    tunable settle spring. (#58,
    #59)
  • drag prop on <Box>. Declarative wrapper that runs useDrag and
    binds its x / y motion values to the transform shorthand. Mirrored on
    both renderers; pointer handler composes with consumer-supplied onPointerDown.
    (#60)
  • UI-thread native drag. New MotionDriver.useDragBacking seam. The
    Reanimated driver implements it when react-native-reanimated and
    react-native-gesture-handler are both installed: Gesture.Pan() on the UI
    thread, bridge back via runOnJS. (#61)

Scroll

  • useScroll. Window scroll (web), container scroll (both), or
    target-relative progress with framer-motion-compatible offset anchors
    ('start end', 'end start', percentages, fractions). Web reads the
    element rect via getBoundingClientRect + ResizeObserver; native pairs
    the ScrollView publisher with a layout snapshot via useScrollTarget.
    (#26,
    #46)

Imperative animate

  • useAnimate (web). Element.animate() under the hood — animations run
    off the main thread where the browser supports it. Returns
    [scope, animate]; targets are ref or selector. (#28)
  • useAnimate (native). Driver-routed via the new
    MotionDriver.useImperativeAnimate method. The default animatedDriver
    drives an Animated.Value per property and writes per-frame via
    setNativeProps. Ref-only targets on native; selector strings resolve to
    a no-op. (#56)

Layout animation

  • useLayoutAnimation + <Box layout>. FLIP-style layout transitions —
    web reads getBoundingClientRect in useLayoutEffect and applies an
    inverse transform; native pairs onLayout with four Animated.Values
    driving translate + scale via Animated.parallel. The declarative
    <Box layout> prop wires the hook for the common case. (#24)

Primitives

  • <Path pathLength>. SVG stroke-drawing animation. Cross-platform; both
    renderers emit pathLength="1" + strokeDasharray="1 1" + a strokeDashoffset
    that walks 1 → 0. Accepts a literal number or a MotionValue<number>.
    (#29)
  • <Stack stagger>. Per-child entry-animation delay. Web reads
    prefers-reduced-motion synchronously and collapses stagger to 0 when on;
    native plumbs a new delayMs field through the driver's useEntryAnimation.
    (#55)

Style props

  • Text flow propswhiteSpace, wordBreak, overflowWrap, hyphens,
    textOverflow. The canonical single-line ellipsis triplet now flows through
    the resolver. (#30)
  • background-* familybackground, backgroundImage, backgroundPosition,
    backgroundRepeat, backgroundSize, backgroundOrigin, backgroundClip,
    backgroundAttachment, backgroundBlendMode. Gradient fills work without
    the style={{ … }} escape hatch. (#33)
  • lines on <Text> — line-clamping via -webkit-line-clamp on web and
    numberOfLines on native. (#31)

Changed

  • UI-thread worklet compose for transform shorthand. Reanimated driver's
    useMotionValueBacking composes the RN transform array inside the worklet
    instead of pre-composing on the JS thread. (#50)
  • Bundle floor. Worst-case dist/index.js gzip grew ~70% on @usemotif/core,
    @usemotif/react, and @usemotif/react-native to fit the new surface. Apps
    that only use <Box> + a theme still tree-shake to ~10 KB gz; consumers
    paying for the motion primitives see ~17 KB gz web / ~20 KB gz native.

Fixed

  • Pseudo-state cascade. _disabled={{ boxShadow: 'none' }} over a base
    boxShadow now actually wins. Previously inline style (specificity 1,0,0,0)
    always beat the pseudo .class:state rule (0,1,1) — silent on bg / color
    where values look the same, biting hard on shadows / gradients.
    (#39)
  • Flex / grid props without display. Dev-only warning when a <Box> has
    flex- or grid-only props (flexDirection, alignItems, gap, …) without
    an explicit display="flex" / inline-flex / grid. Tree-shakes in prod.
    (#32)

Docs

Migration

No breaking changes. v1.0.2 → v1.1.0 is purely additive — existing call
sites compile and behave unchanged. Adopt the motion primitives incrementally;
the surface tree-shakes cleanly, so apps that don't use them pay nothing.