Skip to main content
Solid Primitives 2

Primitive that creates spring physics functions.

StageCategoryVersionLast UpdatedDemo
3Animation1.0.0-next.3 (next)Aug 13, 2026Demo →
Terminal window
npm i @solid-primitives/spring@next

Animate 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. Call set(target) to drive the animation; read value() 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

OptionTypeDefaultDescription
stiffnessnumber0.15How tightly the spring pulls toward the target. Higher = snappier.
dampingnumber0.8How quickly oscillation decays. Lower = more bouncy. 0 = infinite oscillation.
precisionnumber0.01Displacement 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 number
const [progress, setProgress] = createSpring(0);
setProgress(100); // animates 0 → 100
// Tune the physics
const [value, setValue] = createSpring(0, { stiffness: 0.05, damping: 0.6 });
// Animate an object — every key is interpolated independently
const [xy, setXY] = createSpring({ x: 0, y: 0 }, { stiffness: 0.08, damping: 0.2 });
setXY({ x: 200, y: 150 });
// Animate an array
const [rgb, setRgb] = createSpring([255, 0, 0]);
setRgb([0, 128, 255]);
// Animate a Date
const [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 animating

Setter options

// Snap immediately — no animation, Promise resolves right away
setProgress(100, { hard: true });
// Functional setter — receives the current animated value
setProgress(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 window
setProgress(100, { soft: 0.3 }); // 0.3 s soft window

Reactive 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 physics
return <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 promises

Shape 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 time
const [pos, setPos] = createSpring({ x: 0, y: 0 });
setPos({ x: 100, y: 200 });
// NOT OK — key set changed
setPos({ x: 100, y: 200, z: 50 }); // ❌ z was not in the initial value

SSR

Both primitives are SSR-safe. On the server:

  • createSpring returns the initial value unchanged. The setter resolves immediately for hard: true or when stiffness >= 1 and damping >= 1; otherwise it returns a Promise that never resolves (no animation runs).
  • createDerivedSpring returns the initial accessor value unchanged.
  • isAnimating always returns false on the server.

Changelog

See CHANGELOG.md

Solid Primitives 2High-quality reactive primitives for building applications in Solid2
Community
githubdiscord