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: 0.5, 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: 0.5, 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
visualDurationVisual settle time (overrides duration when set); easier to coordinate with tweens
bounce0.30 = no bounce, 1 = very bouncy

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

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.
  • optionsSpringOptions 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.

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.