Primitive that creates spring physics functions.
| Stage | Category | Version | Last Updated | Demo |
|---|---|---|---|---|
| 3 | Animation | 1.0.0-next.3 (next) | Aug 13, 2026 | Demo → |
npm i @solid-primitives/spring@nextAnimate signal values with spring physics. Instead of jumping to the next value instantly, the animated signal "bounces" toward it — producing natural, physically-based motion for numbers, Dates, arrays, and nested objects.
Inspired by and directly ported from svelte/motion.
createSpring— returns a[value, set, extras]tuple. Callset(target)to drive the animation; readvalue()reactively.createDerivedSpring— read-only variant that follows an existing accessor automatically.makeSpring— non-reactive base primitive returning[value, set, extras, cleanup]. Useful outside component trees.
Physics options
| Option | Type | Default | Description |
|---|---|---|---|
stiffness | number | 0.15 | How tightly the spring pulls toward the target. Higher = snappier. |
damping | number | 0.8 | How quickly oscillation decays. Lower = more bouncy. 0 = infinite oscillation. |
precision | number | 0.01 | Displacement threshold below which the spring is considered settled. |
Options can be passed as a plain object or as a reactive accessor (() => options) to change physics parameters dynamically — see Reactive options below.
How to use it
createSpring
import { createSpring } from "@solid-primitives/spring";
// Animate a numberconst [progress, setProgress] = createSpring(0);setProgress(100); // animates 0 → 100
// Tune the physicsconst [value, setValue] = createSpring(0, { stiffness: 0.05, damping: 0.6 });
// Animate an object — every key is interpolated independentlyconst [xy, setXY] = createSpring({ x: 0, y: 0 }, { stiffness: 0.08, damping: 0.2 });setXY({ x: 200, y: 150 });
// Animate an arrayconst [rgb, setRgb] = createSpring([255, 0, 0]);setRgb([0, 128, 255]);
// Animate a Dateconst [date, setDate] = createSpring(new Date("2024-01-01"));setDate(new Date("2025-12-31"));isAnimating
createSpring returns a third element extras containing isAnimating, a reactive boolean accessor that is true while the spring is animating toward its target.
const [progress, setProgress, { isAnimating }] = createSpring(0);setProgress(100);console.log(isAnimating()); // true while animatingSetter options
// Snap immediately — no animation, Promise resolves right awaysetProgress(100, { hard: true });
// Functional setter — receives the current animated valuesetProgress(prev => prev + 10);
// Await settlement before doing something else.// If set() is called again before settling, all pending Promises// resolve together once the animation finishes — none are orphaned.await setProgress(100);console.log("animation finished");soft — gradual launch
Pass soft: true (or a duration in seconds) to start the animation with temporarily
reduced stiffness. The spring "eases in" before regaining full force, preventing a
jarring kick when interrupting an ongoing animation.
setProgress(100, { soft: true }); // ~0.5 s soft windowsetProgress(100, { soft: 0.3 }); // 0.3 s soft windowReactive options
Pass a function as the second argument to createSpring (or makeSpring) to
change physics parameters reactively. The function is called on every animation frame
so option changes take effect immediately without restarting the spring.
import { createMediaQuery } from "@solid-primitives/media";
const reducedMotion = createMediaQuery("(prefers-reduced-motion: reduce)");
const [value, setValue] = createSpring(0, () => ({ stiffness: reducedMotion() ? 1 : 0.15, damping: reducedMotion() ? 1 : 0.8,}));createDerivedSpring
Follows an existing accessor automatically — no need to wire up an effect manually.
import { createSignal } from "solid-js";import { createDerivedSpring } from "@solid-primitives/spring";
const [count, setCount] = createSignal(0);const springCount = createDerivedSpring(count, { stiffness: 0.05 });
// springCount() lags behind count() with spring physicsreturn <p>{springCount().toFixed(1)}</p>;Works with any accessor, including createMemo:
const percent = createMemo(() => (completed() / total()) * 100);const springPercent = createDerivedSpring(percent);makeSpring
Non-reactive base primitive. Returns a 4-tuple [value, set, extras, cleanup].
You are responsible for calling cleanup() when finished — it cancels any
in-flight animation and resolves all pending Promises.
Useful outside component trees: in stores, event-driven modules, or custom
ownership roots where Solid's onCleanup is not available.
import { makeSpring } from "@solid-primitives/spring";
const [value, setValue, { isAnimating }, cleanup] = makeSpring(0);
setValue(100); // starts animating// later…cleanup(); // cancels animation, resolves all pending promisesShape stability
SpringTarget supports numbers, Dates, plain objects, and arrays of those types.
Array length and object key sets must remain stable across set() calls.
Changing structure (adding/removing keys or changing array length) will produce
NaN values in the animated output.
// OK — same keys every timeconst [pos, setPos] = createSpring({ x: 0, y: 0 });setPos({ x: 100, y: 200 });
// NOT OK — key set changedsetPos({ x: 100, y: 200, z: 50 }); // ❌ z was not in the initial valueSSR
Both primitives are SSR-safe. On the server:
createSpringreturns the initial value unchanged. The setter resolves immediately forhard: trueor whenstiffness >= 1anddamping >= 1; otherwise it returns a Promise that never resolves (no animation runs).createDerivedSpringreturns the initial accessor value unchanged.isAnimatingalways returnsfalseon the server.
Changelog
See CHANGELOG.md