useSpring

useSpring creates a spring-animated motion value. Call .set(target) to animate toward a new value with spring physics, or .jump(value) to skip the animation.

<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  const x = useSpring(0)
</script>

<div
  style="transform: translateX({x.current}px)"
  onpointermove={(e) => x.set(e.clientX)}
>
  Follows pointer with spring physics
</div>
<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  const x = useSpring(0)
</script>

<div
  style="transform: translateX({x.current}px)"
  onpointermove={(e) => x.set(e.clientX)}
>
  Follows pointer with spring physics
</div>

Reading the value

The returned object is a real motion-dom MotionValue with a Svelte 5 reactive .current getter on top. There are three ways to read it:

<!-- Svelte 5 idiomatic — recommended -->
<div style="transform: translateX({x.current}px)" />

<!-- Legacy auto-subscribe — still works via .subscribe() shim -->
<div style="transform: translateX({$x}px)" />

<!-- Imperative — for callbacks and event handlers -->
<script>
  function logPosition() {
    console.log(x.get())
  }
</script>
<!-- Svelte 5 idiomatic — recommended -->
<div style="transform: translateX({x.current}px)" />

<!-- Legacy auto-subscribe — still works via .subscribe() shim -->
<div style="transform: translateX({$x}px)" />

<!-- Imperative — for callbacks and event handlers -->
<script>
  function logPosition() {
    console.log(x.get())
  }
</script>

x.current tracks via $state, so reads inside templates, $derived, and $effect re-run automatically when the spring updates. $x is the Svelte 4–style auto-subscribe path — it works because the spring exposes a .subscribe() shim, but .current is preferred for new code.

Usage

From an initial value

Pass a number or unit string to create a spring with that initial value:

<script lang="ts">
  import { useSpring } from '@humanspeak/svelte-motion'

  const scale = useSpring(1)
</script>

<div
  style="transform: scale({scale.current})"
  onpointerenter={() => scale.set(1.2)}
  onpointerleave={() => scale.set(1)}
>
  Hover to scale
</div>
<script lang="ts">
  import { useSpring } from '@humanspeak/svelte-motion'

  const scale = useSpring(1)
</script>

<div
  style="transform: scale({scale.current})"
  onpointerenter={() => scale.set(1.2)}
  onpointerleave={() => scale.set(1)}
>
  Hover to scale
</div>

Following another motion value

Pass another MotionValue (e.g. from useMotionValue, useScroll, or another useSpring) and the spring will animate toward whatever that source emits:

<script>
  import { useMotionValue, useSpring } from '@humanspeak/svelte-motion'

  const target = useMotionValue(0)
  const smoothed = useSpring(target, { stiffness: 100, damping: 20 })
</script>

<input
  type="range"
  min="0"
  max="100"
  oninput={(e) => target.set(+e.currentTarget.value)}
/>
<div style="transform: translateX({smoothed.current}px)">Smooth</div>
<script>
  import { useMotionValue, useSpring } from '@humanspeak/svelte-motion'

  const target = useMotionValue(0)
  const smoothed = useSpring(target, { stiffness: 100, damping: 20 })
</script>

<input
  type="range"
  min="0"
  max="100"
  oninput={(e) => target.set(+e.currentTarget.value)}
/>
<div style="transform: translateX({smoothed.current}px)">Smooth</div>

Following a Svelte readable store

For interop with hooks that still return Svelte stores (useScroll, useTime), pass the store directly — it’s bridged into a motion value internally:

<script>
  import { useScroll, useSpring } from '@humanspeak/svelte-motion'

  const { scrollYProgress } = useScroll()
  const scaleX = useSpring(scrollYProgress)
</script>

<div class="bar" style="transform: scaleX({scaleX.current}); transform-origin: left" />
<script>
  import { useScroll, useSpring } from '@humanspeak/svelte-motion'

  const { scrollYProgress } = useScroll()
  const scaleX = useSpring(scrollYProgress)
</script>

<div class="bar" style="transform: scaleX({scaleX.current}); transform-origin: left" />

With unit strings

useSpring preserves unit suffixes like px, deg, vh, %:

<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  const rotation = useSpring('0deg')
</script>

<button onclick={() => rotation.set('180deg')}>
  <div style="transform: rotate({rotation.current})">Flip</div>
</button>
<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  const rotation = useSpring('0deg')
</script>

<button onclick={() => rotation.set('180deg')}>
  <div style="transform: rotate({rotation.current})">Flip</div>
</button>

Configuration

Customize the spring physics by passing options:

<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  // Snappy spring — stiffer, more damping
  const fast = useSpring(0, { stiffness: 400, damping: 30 })

  // Bouncy spring — stiff but underdamped
  const bouncy = useSpring(0, { stiffness: 200, damping: 10 })

  // Heavy, slow spring
  const heavy = useSpring(0, { stiffness: 100, damping: 20, mass: 3 })

  // Duration-based instead of physics-based
  const timed = useSpring(0, { duration: 500, bounce: 0.25 })
</script>
<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  // Snappy spring — stiffer, more damping
  const fast = useSpring(0, { stiffness: 400, damping: 30 })

  // Bouncy spring — stiff but underdamped
  const bouncy = useSpring(0, { stiffness: 200, damping: 10 })

  // Heavy, slow spring
  const heavy = useSpring(0, { stiffness: 100, damping: 20, mass: 3 })

  // Duration-based instead of physics-based
  const timed = useSpring(0, { duration: 500, bounce: 0.25 })
</script>

Defaults

OptionDefaultNotes
stiffness100Higher = snappier
damping10Higher = less oscillation
mass1Higher = more lethargic
restDelta0.001Position threshold to settle
restSpeed0.01Velocity threshold to settle
velocity0Initial velocity

Note: These defaults match motion-dom — and React framer-motion’s useSpring. They differ from useSpring’s prior svelte-motion defaults (170 / 26); pass explicit options if you need the older feel.

Duration-based options

When you’d rather tune by feel than by physics constants, use the duration API:

OptionDefaultNotes
duration800msTotal animation duration
visualDuration—Visual settle time (overrides duration when set); easier to coordinate with tweens
bounce0.31 = very bouncy, 0 = no overshoot, negative values down to -1 = overdamped (never overshoots, eases in softly)

Setting stiffness, damping, or mass overrides duration / bounce.

// Settles gently without overshooting
const gentle = useSpring(0, { duration: 600, bounce: -0.4 })
// Settles gently without overshooting
const gentle = useSpring(0, { duration: 600, bounce: -0.4 })

skipInitialAnimation

When following a source motion value, the spring normally animates from its initial value to whatever the source emits first. For scroll-restoration or back-navigation scenarios where the first emit is the “current” position rather than a target, set skipInitialAnimation: true so the spring jumps to the first value and only animates on subsequent updates:

<script>
  import { useScroll, useSpring } from '@humanspeak/svelte-motion'

  const { scrollYProgress } = useScroll()
  const smooth = useSpring(scrollYProgress, { skipInitialAnimation: true })
</script>
<script>
  import { useScroll, useSpring } from '@humanspeak/svelte-motion'

  const { scrollYProgress } = useScroll()
  const smooth = useSpring(scrollYProgress, { skipInitialAnimation: true })
</script>

Methods

set(value)

Animate toward a new target value:

<script>
  const x = useSpring(0)

  x.set(200) // smoothly animates to 200
</script>
<script>
  const x = useSpring(0)

  x.set(200) // smoothly animates to 200
</script>

If the spring is mid-animation, the existing velocity carries over into the new target — no visible discontinuity.

jump(value)

Immediately set the value without animation:

<script>
  const x = useSpring(0)

  x.jump(200) // instantly at 200, no animation
</script>
<script>
  const x = useSpring(0)

  x.jump(200) // instantly at 200, no animation
</script>

Useful for resetting state or initializing to a known position.

get()

Read the current value imperatively:

<script>
  const x = useSpring(0)

  function logPosition() {
    console.log(x.get())
  }
</script>
<script>
  const x = useSpring(0)

  function logPosition() {
    console.log(x.get())
  }
</script>

Prefer x.current inside reactive scopes (templates, $derived, $effect); use .get() in event handlers and other one-shot reads.

on(event, callback)

Subscribe to motion value events. Returns an unsubscribe function.

<script>
  import { useSpring } from '@humanspeak/svelte-motion'
  import { onDestroy } from 'svelte'

  const x = useSpring(0)

  const off = x.on('change', (v) => console.log('changed', v))
  onDestroy(off)

  // Other events:
  x.on('animationStart', () => console.log('spring started'))
  x.on('animationComplete', () => console.log('spring settled'))
</script>
<script>
  import { useSpring } from '@humanspeak/svelte-motion'
  import { onDestroy } from 'svelte'

  const x = useSpring(0)

  const off = x.on('change', (v) => console.log('changed', v))
  onDestroy(off)

  // Other events:
  x.on('animationStart', () => console.log('spring started'))
  x.on('animationComplete', () => console.log('spring settled'))
</script>

destroy()

Tear down the spring early. Normally not needed — the spring auto-cleans up when its surrounding component unmounts.

How it works

useSpring returns a MotionValue from motion-dom (the same primitive used by every other motion value in this library). The spring physics are computed by motion-dom’s attachFollow + JSAnimation — the same engine React framer-motion uses, so behavior, defaults, and option semantics are 1:1.

The Svelte 5 layer adds:

  • A .current getter backed by $state, kept in sync with the motion value’s change event so reads inside reactive scopes track automatically.
  • A .subscribe(run) shim implementing the Svelte readable store contract, so legacy $store syntax and store-consumers (useTransform function form, useVelocity, derived(...), etc.) keep working.
  • Lifecycle binding via $effect, so the spring auto-cleans when the component unmounts.

Performance

  • On-demand: The animation loop only runs while the spring is in motion.
  • Auto-settle: Stops computing once both position delta and velocity fall below restDelta / restSpeed.
  • Velocity handoff: Mid-animation retargets carry velocity into the new spring, so rapid input doesn’t produce visual jumps.
  • SSR-safe: Returns a static motion value with no-op .set / .jump on the server.

Common patterns

Pointer tracking

<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  const x = useSpring(0, { stiffness: 300, damping: 25 })
  const y = useSpring(0, { stiffness: 300, damping: 25 })
</script>

<svelte:window
  onpointermove={(e) => { x.set(e.clientX); y.set(e.clientY) }}
/>

<div style="transform: translate({x.current}px, {y.current}px)">
  Cursor follower
</div>
<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  const x = useSpring(0, { stiffness: 300, damping: 25 })
  const y = useSpring(0, { stiffness: 300, damping: 25 })
</script>

<svelte:window
  onpointermove={(e) => { x.set(e.clientX); y.set(e.clientY) }}
/>

<div style="transform: translate({x.current}px, {y.current}px)">
  Cursor follower
</div>

Toggle animation

<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  const rotation = useSpring(0, { stiffness: 200, damping: 15 })
  let toggled = $state(false)
</script>

<button onclick={() => { toggled = !toggled; rotation.set(toggled ? 180 : 0) }}>
  <div style="transform: rotate({rotation.current}deg)">Toggle</div>
</button>
<script>
  import { useSpring } from '@humanspeak/svelte-motion'

  const rotation = useSpring(0, { stiffness: 200, damping: 15 })
  let toggled = $state(false)
</script>

<button onclick={() => { toggled = !toggled; rotation.set(toggled ? 180 : 0) }}>
  <div style="transform: rotate({rotation.current}deg)">Toggle</div>
</button>

With useVelocity

Track the velocity of a spring for momentum-based effects:

<script>
  import { useSpring, useVelocity, useTransform } from '@humanspeak/svelte-motion'

  const x = useSpring(0)
  const xVelocity = useVelocity(x)
  const skew = useTransform(xVelocity, [-1000, 0, 1000], [-20, 0, 20])
</script>

<div style="transform: translateX({x.current}px) skewX({$skew}deg)">
  Momentum skew
</div>
<script>
  import { useSpring, useVelocity, useTransform } from '@humanspeak/svelte-motion'

  const x = useSpring(0)
  const xVelocity = useVelocity(x)
  const skew = useTransform(xVelocity, [-1000, 0, 1000], [-20, 0, 20])
</script>

<div style="transform: translateX({x.current}px) skewX({$skew}deg)">
  Momentum skew
</div>

useVelocity and useTransform still return Svelte stores in this release ($skew). They’ll migrate to motion values in a future release; the .current pattern will apply there too.

API Reference

Signature

useSpring(
  source: number | string | MotionValue<number | string> | Readable<number | string>,
  options?: UseSpringOptions
): SpringMotionValue<number | string>
useSpring(
  source: number | string | MotionValue<number | string> | Readable<number | string>,
  options?: UseSpringOptions
): SpringMotionValue<number | string>

Parameters

  • source — Initial value, unit string, another MotionValue to follow, or a Svelte readable store to follow.
  • options — SpringOptions plus skipInitialAnimation. See Configuration.

Returns

A SpringMotionValue<T> — a real motion-dom MotionValue augmented with:

  • current T (getter) — Svelte 5 reactive read backed by $state.
  • set(value) — animate toward a new target.
  • jump(value) — set immediately, no animation.
  • get() — imperative current-value read.
  • on(event, cb) — subscribe to 'change' / 'animationStart' / 'animationComplete' / 'animationCancel' / 'destroy'.
  • subscribe(run) — Svelte readable store contract for $store syntax and store-consumers.
  • destroy() — early teardown.
  • All other MotionValue methods from motion-dom (getVelocity, etc.).

When to use

  • Smooth value transitions — animate any numeric value with natural-feeling motion.
  • Pointer following — track cursor or touch position with spring physics.
  • Interactive toggles — smoothly animate between states on user interaction.
  • Smoothing scroll progress — wrap useScroll’s output for buttery progress bars and parallax.

For gesture-driven springs, combine with event handlers. For time-based animations, see useTime + useTransform.

Live example

Try useFollowValue: four spring settings and two tweens chase the same pointer target. Use the arrow keys or Replay to compare their responses, and Reset to return to the center. useSpring delegates to the same follower API with a spring transition.

See also

  • useVelocity — track the velocity of a spring or any motion value.
  • useTransform — map spring values to other ranges.
  • useMotionTemplate — compose CSS strings from spring values.
  • useTime — reactive time source for continuous animations.

Based on Motion’s useSpring API; physics delegated to motion-dom’s attachFollow.