or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

motion.mddocs/reference/

svelte/motion

The svelte/motion module provides physics-based animations and smooth interpolations for creating natural-feeling UI transitions. It includes modern class-based APIs introduced in Svelte 5.8+ alongside legacy store-based functions for backward compatibility.

Overview

Motion in Svelte comes in two flavors:

  • Spring: Physics-based motion that simulates spring mechanics with stiffness, damping, and momentum
  • Tween: Time-based motion that interpolates between values over a specified duration

Both support complex data types (numbers, objects, arrays, colors, etc.) and integrate seamlessly with Svelte's reactivity system.

Classes

Spring<T> { .api }

A wrapper for a value that behaves in a spring-like fashion. Changes to spring.target will cause spring.current to move towards it over time, taking account of the spring.stiffness and spring.damping parameters.

Since: 5.8.0

class Spring<T> {
  constructor(value: T, options?: SpringOpts);
  static of<U>(fn: () => U, options?: SpringOpts): Spring<U>;

  set(value: T, options?: SpringUpdateOpts): Promise<void>;

  target: T;
  get current(): T;
  damping: number;
  precision: number;
  stiffness: number;
}

Constructor

new Spring(value: T, options?: SpringOpts)

Creates a new spring with an initial value.

Parameters:

  • value - Initial value for both target and current
  • options - Optional spring physics parameters

Example:

<script>
  import { Spring } from 'svelte/motion';

  const spring = new Spring(0);
</script>

<input type="range" bind:value={spring.target} />
<input type="range" bind:value={spring.current} disabled />

Static Methods

Spring.of() { .api }
static of<U>(fn: () => U, options?: SpringOpts): Spring<U>

Create a spring whose value is bound to the return value of fn. This must be called inside an effect root (for example, during component initialisation).

Parameters:

  • fn - Function that returns the value to track
  • options - Optional spring physics parameters

Example:

<script>
  import { Spring } from 'svelte/motion';

  let { number } = $props();

  const spring = Spring.of(() => number);
</script>

<p>Input: {number}</p>
<p>Spring: {spring.current}</p>

Properties

target { .api }
target: T

The end value of the spring. Setting this property will cause the spring to animate towards the new value.

Note: This property only exists on the Spring class, not the legacy spring store.

Example:

<script>
  import { Spring } from 'svelte/motion';

  const position = new Spring({ x: 0, y: 0 });

  function moveToCorner() {
    position.target = { x: 100, y: 100 };
  }
</script>

<button onclick={moveToCorner}>Move</button>
<div style="transform: translate({position.current.x}px, {position.current.y}px)">
  Box
</div>
current { .api }
get current(): T

The current value of the spring (read-only). This value updates automatically as the spring animates.

Note: This property only exists on the Spring class, not the legacy spring store.

stiffness { .api }
stiffness: number

Controls how "stiff" the spring is. Higher values make the spring respond more quickly.

Default: 0.15

Range: 0 to 1

Example:

<script>
  import { Spring } from 'svelte/motion';

  const spring = new Spring(0);
</script>

<input type="range" min="0" max="1" step="0.01" bind:value={spring.stiffness} />
<p>Stiffness: {spring.stiffness}</p>
damping { .api }
damping: number

Controls how much the spring resists motion. Higher values make the spring settle more quickly with less oscillation.

Default: 0.8

Range: 0 to 1

Example:

<script>
  import { Spring } from 'svelte/motion';

  const spring = new Spring(0);
</script>

<input type="range" min="0" max="1" step="0.01" bind:value={spring.damping} />
<p>Damping: {spring.damping}</p>
precision { .api }
precision: number

Determines when the spring is considered to have "settled". The spring will stop animating when the distance between current and target is less than this value.

Default: 0.01

Methods

set() { .api }
set(value: T, options?: SpringUpdateOpts): Promise<void>

Sets spring.target to value and returns a Promise that resolves if and when spring.current catches up to it.

Parameters:

  • value - The new target value
  • options - Optional update options

Options:

  • instant?: boolean - If true, spring.current immediately matches spring.target
  • preserveMomentum?: number - The spring will continue on its current trajectory for the specified number of milliseconds. Useful for "fling" gestures.

Returns: Promise that resolves when the spring settles

Example:

<script>
  import { Spring } from 'svelte/motion';

  const spring = new Spring(0);

  async function jumpToValue() {
    await spring.set(100, { instant: true });
    console.log('Jumped instantly!');
  }

  async function animateToValue() {
    await spring.set(100);
    console.log('Animation complete!');
  }
</script>

<button onclick={jumpToValue}>Jump</button>
<button onclick={animateToValue}>Animate</button>

Usage with Runes

Springs integrate naturally with Svelte 5's runes system:

<script>
  import { Spring } from 'svelte/motion';

  let count = $state(0);
  const springCount = Spring.of(() => count, {
    stiffness: 0.1,
    damping: 0.5
  });
</script>

<button onclick={() => count++}>
  Clicks: {count}
</button>

<p>Spring value: {springCount.current.toFixed(2)}</p>

Physics-Based Animation Example

<script>
  import { Spring } from 'svelte/motion';

  const coords = new Spring(
    { x: 50, y: 50 },
    { stiffness: 0.1, damping: 0.3 }
  );

  function handleMouseMove(event) {
    coords.target = {
      x: event.clientX,
      y: event.clientY
    };
  }
</script>

<svelte:window on:mousemove={handleMouseMove} />

<div
  class="cursor"
  style="left: {coords.current.x}px; top: {coords.current.y}px"
/>

<style>
  .cursor {
    position: fixed;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #ff3e00;
    transform: translate(-50%, -50%);
  }
</style>

Tween<T> { .api }

A wrapper for a value that tweens smoothly to its target value. Changes to tween.target will cause tween.current to move towards it over time, taking account of the delay, duration and easing options.

Since: 5.8.0

class Tween<T> {
  constructor(value: T, options?: TweenedOptions<T>);
  static of<U>(fn: () => U, options?: TweenedOptions<U>): Tween<U>;

  set(value: T, options?: TweenedOptions<T>): Promise<void>;

  target: T;
  get current(): T;
}

Constructor

new Tween(value: T, options?: TweenedOptions<T>)

Creates a new tween with an initial value.

Parameters:

  • value - Initial value for both target and current
  • options - Optional tween parameters

Example:

<script>
  import { Tween } from 'svelte/motion';

  const tween = new Tween(0);
</script>

<input type="range" bind:value={tween.target} />
<input type="range" bind:value={tween.current} disabled />

Static Methods

Tween.of() { .api }
static of<U>(fn: () => U, options?: TweenedOptions<U>): Tween<U>

Create a tween whose value is bound to the return value of fn. This must be called inside an effect root (for example, during component initialisation).

Parameters:

  • fn - Function that returns the value to track
  • options - Optional tween parameters

Example:

<script>
  import { Tween } from 'svelte/motion';
  import { cubicOut } from 'svelte/easing';

  let { number } = $props();

  const tween = Tween.of(() => number, {
    duration: 500,
    easing: cubicOut
  });
</script>

<p>Input: {number}</p>
<p>Tween: {tween.current}</p>

Properties

target { .api }
target: T

The end value of the tween. Setting this property will cause the tween to animate towards the new value.

Example:

<script>
  import { Tween } from 'svelte/motion';

  const opacity = new Tween(0);

  function fadeIn() {
    opacity.target = 1;
  }

  function fadeOut() {
    opacity.target = 0;
  }
</script>

<button onclick={fadeIn}>Fade In</button>
<button onclick={fadeOut}>Fade Out</button>

<div style="opacity: {opacity.current}">
  Fading element
</div>
current { .api }
get current(): T

The current value of the tween (read-only). This value updates automatically as the tween animates.

Methods

set() { .api }
set(value: T, options?: TweenedOptions<T>): Promise<void>

Sets tween.target to value and returns a Promise that resolves if and when tween.current catches up to it.

If options are provided, they will override the tween's defaults.

Parameters:

  • value - The new target value
  • options - Optional tween parameters that override defaults

Returns: Promise that resolves when the tween completes

Example:

<script>
  import { Tween } from 'svelte/motion';
  import { elasticOut } from 'svelte/easing';

  const tween = new Tween(0);

  async function animate() {
    // Override default options for this animation
    await tween.set(100, {
      duration: 1000,
      easing: elasticOut
    });
    console.log('Animation complete!');
  }
</script>

<button onclick={animate}>Animate</button>

Interpolating Complex Values

Tweens can interpolate any value type with a custom interpolator:

<script>
  import { Tween } from 'svelte/motion';
  import { cubicInOut } from 'svelte/easing';

  // Custom interpolator for colors
  function interpolateColor(from, to) {
    const fromRGB = hexToRgb(from);
    const toRGB = hexToRgb(to);

    return (t) => {
      const r = Math.round(fromRGB.r + (toRGB.r - fromRGB.r) * t);
      const g = Math.round(fromRGB.g + (toRGB.g - fromRGB.g) * t);
      const b = Math.round(fromRGB.b + (toRGB.b - fromRGB.b) * t);
      return `rgb(${r}, ${g}, ${b})`;
    };
  }

  const color = new Tween('#ff3e00', {
    duration: 500,
    easing: cubicInOut,
    interpolate: interpolateColor
  });

  function changeColor() {
    color.target = '#40b3ff';
  }
</script>

<button onclick={changeColor}>Change Color</button>
<div style="background: {color.current}; width: 100px; height: 100px;"></div>

Dynamic Duration Example

<script>
  import { Tween } from 'svelte/motion';

  const progress = new Tween(0, {
    duration: (from, to) => {
      // Duration proportional to distance
      return Math.abs(to - from) * 10;
    }
  });
</script>

<button onclick={() => progress.target = 0}>0%</button>
<button onclick={() => progress.target = 50}>50%</button>
<button onclick={() => progress.target = 100}>100%</button>

<div class="progress-bar">
  <div class="fill" style="width: {progress.current}%"></div>
</div>

Constants

prefersReducedMotion { .api }

const prefersReducedMotion: MediaQuery

A media query that matches if the user prefers reduced motion.

Since: 5.7.0

Type: MediaQuery (from svelte/reactivity)

Properties:

  • current: boolean - true if the user prefers reduced motion, false otherwise

Example:

<script>
  import { prefersReducedMotion } from 'svelte/motion';
  import { fly } from 'svelte/transition';

  let visible = $state(false);
</script>

<button onclick={() => visible = !visible}>
  toggle
</button>

{#if visible}
  <p transition:fly={{ y: prefersReducedMotion.current ? 0 : 200 }}>
    flies in, unless the user prefers reduced motion
  </p>
{/if}

Accessibility Usage:

<script>
  import { Spring } from 'svelte/motion';
  import { prefersReducedMotion } from 'svelte/motion';

  const spring = new Spring(0, {
    // Reduce spring stiffness if user prefers reduced motion
    stiffness: prefersReducedMotion.current ? 1 : 0.1,
    damping: prefersReducedMotion.current ? 1 : 0.3
  });
</script>

Deprecated Functions

The following functions are provided for backward compatibility with Svelte 4. New code should use the Spring and Tween classes instead.

spring() { .api }

function spring<T = any>(
  value?: T,
  opts?: SpringOpts
): Spring<T>

The spring function in Svelte creates a store whose value is animated, with a motion that simulates the behavior of a spring. This means when the value changes, instead of transitioning at a steady rate, it "bounces" like a spring would, depending on the physics parameters provided.

Deprecated: Use Spring class instead

Parameters:

  • value - Initial value
  • opts - Spring physics options

Returns: Legacy Spring store with subscribe(), set(), and update() methods

Example:

<script>
  import { spring } from 'svelte/motion';

  // Legacy store-based API
  const coords = spring({ x: 50, y: 50 }, {
    stiffness: 0.1,
    damping: 0.25
  });

  // Subscribe to changes
  const unsubscribe = coords.subscribe(value => {
    console.log('coords:', value);
  });

  // Update value
  coords.set({ x: 100, y: 100 });
</script>

<div style="transform: translate({$coords.x}px, {$coords.y}px)">
  Legacy spring
</div>

Migration to Spring class:

<!-- Before (Svelte 4 style) -->
<script>
  import { spring } from 'svelte/motion';
  const coords = spring({ x: 50, y: 50 });
</script>
<div style="transform: translate({$coords.x}px, {$coords.y}px)">

<!-- After (Svelte 5 style) -->
<script>
  import { Spring } from 'svelte/motion';
  const coords = new Spring({ x: 50, y: 50 });
</script>
<div style="transform: translate({coords.current.x}px, {coords.current.y}px)">

tweened() { .api }

function tweened<T>(
  value?: T,
  defaults?: TweenedOptions<T>
): Tweened<T>

A tweened store in Svelte is a special type of store that provides smooth transitions between state values over time.

Deprecated: Use Tween class instead

Parameters:

  • value - Initial value
  • defaults - Default tween options

Returns: Legacy Tweened store with subscribe(), set(), and update() methods

Example:

<script>
  import { tweened } from 'svelte/motion';
  import { cubicOut } from 'svelte/easing';

  // Legacy store-based API
  const progress = tweened(0, {
    duration: 400,
    easing: cubicOut
  });

  // Subscribe to changes
  progress.subscribe(value => {
    console.log('progress:', value);
  });

  // Update value
  progress.set(100);
</script>

<progress value={$progress} max="100"></progress>

Migration to Tween class:

<!-- Before (Svelte 4 style) -->
<script>
  import { tweened } from 'svelte/motion';
  const progress = tweened(0);
</script>
<progress value={$progress} max="100"></progress>

<!-- After (Svelte 5 style) -->
<script>
  import { Tween } from 'svelte/motion';
  const progress = new Tween(0);
</script>
<progress value={progress.current} max="100"></progress>

Types

SpringOpts { .api }

interface SpringOpts {
  stiffness?: number;
  damping?: number;
  precision?: number;
}

Configuration options for spring physics.

Properties:

  • stiffness?: number - Controls how "stiff" the spring is (default: 0.15, range: 0 to 1)
  • damping?: number - Controls how much the spring resists motion (default: 0.8, range: 0 to 1)
  • precision?: number - Threshold for when the spring is considered settled (default: 0.01)

Example:

const springOpts: SpringOpts = {
  stiffness: 0.2,  // Slightly stiffer
  damping: 0.4,    // Less damping, more bounce
  precision: 0.001 // More precise settling
};

const spring = new Spring(0, springOpts);

SpringUpdateOpts { .api }

interface SpringUpdateOpts {
  hard?: any; // @deprecated Only for legacy spring store
  soft?: string | number | boolean; // @deprecated Only for legacy spring store
  instant?: boolean;
  preserveMomentum?: number;
}

Options for updating a spring value.

Properties:

  • instant?: boolean - If true, immediately set current to target without animation (Spring class only)
  • preserveMomentum?: number - Continue on current trajectory for specified milliseconds before targeting new value (Spring class only)
  • hard?: any - Deprecated: Only use for legacy spring store; does nothing on Spring class
  • soft?: string | number | boolean - Deprecated: Only use for legacy spring store; does nothing on Spring class

Example:

<script>
  import { Spring } from 'svelte/motion';

  const position = new Spring(0);

  // Instant update
  position.set(100, { instant: true });

  // Preserve momentum (for fling gestures)
  position.set(100, { preserveMomentum: 500 });
</script>

TweenedOptions<T> { .api }

interface TweenedOptions<T> {
  delay?: number;
  duration?: number | ((from: T, to: T) => number);
  easing?: (t: number) => number;
  interpolate?: (a: T, b: T) => (t: number) => T;
}

Configuration options for tweened animations.

Properties:

  • delay?: number - Milliseconds before animation starts (default: 0)
  • duration?: number | ((from: T, to: T) => number) - Animation duration in milliseconds, or function that calculates duration based on start/end values (default: 400)
  • easing?: (t: number) => number - Easing function that maps linear time (0-1) to eased time (default: linear)
  • interpolate?: (a: T, b: T) => (t: number) => T - Custom interpolation function for complex value types

Example:

<script>
  import { Tween } from 'svelte/motion';
  import { elasticOut } from 'svelte/easing';

  const options: TweenedOptions<number> = {
    delay: 100,
    duration: 800,
    easing: elasticOut
  };

  const value = new Tween(0, options);
</script>

Custom Interpolator Example:

interface Point {
  x: number;
  y: number;
}

function interpolatePoint(a: Point, b: Point): (t: number) => Point {
  return (t) => ({
    x: a.x + (b.x - a.x) * t,
    y: a.y + (b.y - a.y) * t
  });
}

const point = new Tween<Point>(
  { x: 0, y: 0 },
  { interpolate: interpolatePoint }
);

Legacy Spring<T> Interface { .api }

interface Spring<T> extends Readable<T> {
  set(new_value: T, opts?: SpringUpdateOpts): Promise<void>;
  update(fn: Updater<T>, opts?: SpringUpdateOpts): Promise<void>; // @deprecated
  subscribe(fn: (value: T) => void): Unsubscriber; // @deprecated
  precision: number;
  damping: number;
  stiffness: number;
}

Legacy interface for the store-based spring() function. Due to declaration merging, this interface shares a name with the Spring class but represents the older store-based API.

Note: In Svelte 6, this type definition will be removed. Use the Spring class instead.

Deprecated Properties:

  • update() - Only exists on legacy spring store, not Spring class
  • subscribe() - Only exists on legacy spring store, not Spring class

Legacy Tweened<T> Interface { .api }

interface Tweened<T> extends Readable<T> {
  set(value: T, opts?: TweenedOptions<T>): Promise<void>;
  update(updater: Updater<T>, opts?: TweenedOptions<T>): Promise<void>;
}

Legacy interface for the store-based tweened() function.

Methods:

  • set(value: T, opts?: TweenedOptions<T>): Promise<void> - Set new value with optional options override
  • update(updater: Updater<T>, opts?: TweenedOptions<T>): Promise<void> - Update value based on current value
  • subscribe(fn: (value: T) => void): Unsubscriber - Subscribe to value changes (inherited from Readable)

Complete Examples

Interactive Spring Physics Playground

<script>
  import { Spring } from 'svelte/motion';

  const position = new Spring(50, {
    stiffness: 0.15,
    damping: 0.8,
    precision: 0.01
  });

  let targetPosition = $state(50);

  $effect(() => {
    position.target = targetPosition;
  });
</script>

<div class="controls">
  <label>
    Target Position: {targetPosition}
    <input type="range" bind:value={targetPosition} min="0" max="100" />
  </label>

  <label>
    Stiffness: {position.stiffness.toFixed(2)}
    <input type="range" bind:value={position.stiffness} min="0" max="1" step="0.01" />
  </label>

  <label>
    Damping: {position.damping.toFixed(2)}
    <input type="range" bind:value={position.damping} min="0" max="1" step="0.01" />
  </label>
</div>

<div class="visualization">
  <div class="track">
    <div
      class="target"
      style="left: {targetPosition}%"
    />
    <div
      class="current"
      style="left: {position.current}%"
    />
  </div>
</div>

<style>
  .controls {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    margin-bottom: 2rem;
  }

  .track {
    position: relative;
    height: 60px;
    background: #f0f0f0;
    border-radius: 30px;
  }

  .target,
  .current {
    position: absolute;
    top: 50%;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    transform: translate(-50%, -50%);
  }

  .target {
    background: #ccc;
    z-index: 1;
  }

  .current {
    background: #ff3e00;
    z-index: 2;
  }
</style>

Animated Dashboard with Multiple Tweens

<script>
  import { Tween } from 'svelte/motion';
  import { cubicOut } from 'svelte/easing';

  let data = $state([
    { label: 'Sales', value: 0 },
    { label: 'Users', value: 0 },
    { label: 'Revenue', value: 0 }
  ]);

  const tweenedData = data.map(item => ({
    label: item.label,
    tween: new Tween(0, { duration: 1000, easing: cubicOut })
  }));

  function updateData() {
    data = [
      { label: 'Sales', value: Math.random() * 100 },
      { label: 'Users', value: Math.random() * 100 },
      { label: 'Revenue', value: Math.random() * 100 }
    ];

    tweenedData.forEach((item, i) => {
      item.tween.target = data[i].value;
    });
  }
</script>

<button onclick={updateData}>Refresh Data</button>

<div class="dashboard">
  {#each tweenedData as item}
    <div class="card">
      <h3>{item.label}</h3>
      <div class="value">{item.tween.current.toFixed(1)}</div>
      <div class="bar">
        <div class="fill" style="width: {item.tween.current}%"></div>
      </div>
    </div>
  {/each}
</div>

Accessible Motion with Reduced Motion Support

<script>
  import { Spring } from 'svelte/motion';
  import { prefersReducedMotion } from 'svelte/motion';

  // Adjust physics based on user preference
  const springOpts = $derived({
    stiffness: prefersReducedMotion.current ? 1 : 0.1,
    damping: prefersReducedMotion.current ? 1 : 0.3,
    precision: 0.01
  });

  let coords = new Spring({ x: 0, y: 0 }, springOpts);

  function handleClick(event) {
    coords.set(
      { x: event.clientX, y: event.clientY },
      { instant: prefersReducedMotion.current }
    );
  }
</script>

<svelte:window onclick={handleClick} />

<div
  class="follower"
  style="
    left: {coords.current.x}px;
    top: {coords.current.y}px;
  "
/>

<div class="info">
  Motion preference: {prefersReducedMotion.current ? 'Reduced' : 'Full'}
</div>

Best Practices

1. Choose the Right Motion Type

  • Use Spring for:

    • Interactive elements (drag and drop, gestures)
    • Physics-based animations
    • When you want natural-feeling motion
    • When the final value isn't time-critical
  • Use Tween for:

    • Predictable, time-based animations
    • UI transitions with known duration
    • Animations that must complete at specific times
    • Simple interpolations

2. Respect User Preferences

Always check prefersReducedMotion and adjust animations accordingly:

<script>
  import { Spring } from 'svelte/motion';
  import { prefersReducedMotion } from 'svelte/motion';

  const spring = new Spring(0, {
    stiffness: prefersReducedMotion.current ? 1 : 0.1,
    damping: prefersReducedMotion.current ? 1 : 0.3
  });
</script>

3. Use .of() for Reactive Values

When tracking reactive state, use the .of() static method:

<script>
  import { Spring } from 'svelte/motion';

  let count = $state(0);

  // Automatically tracks changes to count
  const springCount = Spring.of(() => count);
</script>

4. Clean Up with Promises

Use the returned promises to chain animations or clean up:

<script>
  import { Tween } from 'svelte/motion';

  const tween = new Tween(0);

  async function sequence() {
    await tween.set(100);
    await tween.set(0);
    console.log('Sequence complete!');
  }
</script>

5. Optimize Performance

For many animated elements, consider using CSS transforms and GPU acceleration:

<script>
  import { Spring } from 'svelte/motion';

  const x = new Spring(0);
</script>

<div style="transform: translateX({x.current}px)">
  <!-- GPU-accelerated -->
</div>

Migration Guide

From spring() to Spring

<!-- Before -->
<script>
  import { spring } from 'svelte/motion';
  const value = spring(0);
  $: value.set(newValue);
</script>
{$value}

<!-- After -->
<script>
  import { Spring } from 'svelte/motion';
  const value = new Spring(0);
  $effect(() => {
    value.target = newValue;
  });
</script>
{value.current}

From tweened() to Tween

<!-- Before -->
<script>
  import { tweened } from 'svelte/motion';
  const value = tweened(0);
  $: value.set(newValue);
</script>
{$value}

<!-- After -->
<script>
  import { Tween } from 'svelte/motion';
  const value = new Tween(0);
  $effect(() => {
    value.target = newValue;
  });
</script>
{value.current}

Store Subscription to Direct Access

<!-- Before -->
<script>
  import { spring } from 'svelte/motion';
  const coords = spring({ x: 0, y: 0 });

  coords.subscribe(value => {
    console.log(value.x, value.y);
  });
</script>

<!-- After -->
<script>
  import { Spring } from 'svelte/motion';
  const coords = new Spring({ x: 0, y: 0 });

  $effect(() => {
    console.log(coords.current.x, coords.current.y);
  });
</script>