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.
Motion in Svelte comes in two flavors:
Both support complex data types (numbers, objects, arrays, colors, etc.) and integrate seamlessly with Svelte's reactivity system.
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;
}new Spring(value: T, options?: SpringOpts)Creates a new spring with an initial value.
Parameters:
value - Initial value for both target and currentoptions - Optional spring physics parametersExample:
<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 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 trackoptions - Optional spring physics parametersExample:
<script>
import { Spring } from 'svelte/motion';
let { number } = $props();
const spring = Spring.of(() => number);
</script>
<p>Input: {number}</p>
<p>Spring: {spring.current}</p>target: TThe 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>get current(): TThe 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: numberControls 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: numberControls 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: numberDetermines 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
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 valueoptions - Optional update optionsOptions:
instant?: boolean - If true, spring.current immediately matches spring.targetpreserveMomentum?: 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>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><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>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;
}new Tween(value: T, options?: TweenedOptions<T>)Creates a new tween with an initial value.
Parameters:
value - Initial value for both target and currentoptions - Optional tween parametersExample:
<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 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 trackoptions - Optional tween parametersExample:
<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>target: TThe 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>get current(): TThe current value of the tween (read-only). This value updates automatically as the tween animates.
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 valueoptions - Optional tween parameters that override defaultsReturns: 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>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><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>const prefersReducedMotion: MediaQueryA 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 otherwiseExample:
<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>The following functions are provided for backward compatibility with Svelte 4. New code should use the Spring and Tween classes instead.
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 valueopts - Spring physics optionsReturns: 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)">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 valuedefaults - Default tween optionsReturns: 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>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);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 classsoft?: string | number | boolean - Deprecated: Only use for legacy spring store; does nothing on Spring classExample:
<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>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 typesExample:
<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 }
);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 classsubscribe() - Only exists on legacy spring store, not Spring classinterface 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 overrideupdate(updater: Updater<T>, opts?: TweenedOptions<T>): Promise<void> - Update value based on current valuesubscribe(fn: (value: T) => void): Unsubscriber - Subscribe to value changes (inherited from Readable)<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><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><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>Use Spring for:
Use Tween for:
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>.of() for Reactive ValuesWhen 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>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>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><!-- 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}<!-- 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}<!-- 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>