Complete API reference for Svelte's built-in transition and animation system.
Svelte provides a powerful system for creating smooth, performant animations:
The transition module provides functions that create visual effects when elements enter or leave the DOM.
Animates a blur filter alongside an element's opacity.
function blur(
node: Element,
params?: BlurParams
): TransitionConfigParameters:
interface BlurParams {
delay?: number;
duration?: number;
easing?: EasingFunction;
amount?: number | string;
opacity?: number;
}0)400)cubicInOut)5)0)Example:
<script>
import { blur } from 'svelte/transition';
let visible = $state(false);
</script>
<button onclick={() => visible = !visible}>
Toggle
</button>
{#if visible}
<div transition:blur={{ amount: 10 }}>
Blurs in and out
</div>
{/if}Custom easing:
<script>
import { blur } from 'svelte/transition';
import { elasticOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:blur={{ duration: 600, easing: elasticOut, amount: '1rem' }}>
Custom blur effect
</div>
{/if}Animates the opacity of an element from 0 to the current opacity for in transitions and from the current opacity to 0 for out transitions.
function fade(
node: Element,
params?: FadeParams
): TransitionConfigParameters:
interface FadeParams {
delay?: number;
duration?: number;
easing?: EasingFunction;
}0)400)linear)Example:
<script>
import { fade } from 'svelte/transition';
let visible = $state(false);
</script>
<button onclick={() => visible = !visible}>
Toggle
</button>
{#if visible}
<div transition:fade>
Fades in and out
</div>
{/if}Separate in/out transitions:
<script>
import { fade } from 'svelte/transition';
</script>
{#if visible}
<div
in:fade={{ duration: 200 }}
out:fade={{ duration: 600 }}
>
Fast fade in, slow fade out
</div>
{/if}Animates the x and y positions and the opacity of an element. in transitions animate from the provided values to the element's default values. out transitions animate from the element's default values to the provided values.
function fly(
node: Element,
params?: FlyParams
): TransitionConfigParameters:
interface FlyParams {
delay?: number;
duration?: number;
easing?: EasingFunction;
x?: number | string;
y?: number | string;
opacity?: number;
}0)400)cubicOut)0)0)0)Example:
<script>
import { fly } from 'svelte/transition';
let visible = $state(false);
</script>
<button onclick={() => visible = !visible}>
Toggle
</button>
{#if visible}
<div transition:fly={{ y: 200, duration: 500 }}>
Flies in from below
</div>
{/if}Horizontal fly with custom easing:
<script>
import { fly } from 'svelte/transition';
import { elasticOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:fly={{ x: -100, duration: 600, easing: elasticOut }}>
Bounces in from the left
</div>
{/if}Using CSS units:
{#if visible}
<div transition:fly={{ y: '50vh', x: '2rem' }}>
Flies with relative units
</div>
{/if}Slides an element in and out.
function slide(
node: Element,
params?: SlideParams
): TransitionConfigParameters:
interface SlideParams {
delay?: number;
duration?: number;
easing?: EasingFunction;
axis?: 'x' | 'y';
}0)400)cubicOut)'x' or 'y' (default: 'y')Example:
<script>
import { slide } from 'svelte/transition';
let visible = $state(false);
</script>
<button onclick={() => visible = !visible}>
Toggle
</button>
{#if visible}
<div transition:slide>
Slides down and up
</div>
{/if}Horizontal slide:
<script>
import { slide } from 'svelte/transition';
</script>
{#if visible}
<div transition:slide={{ axis: 'x', duration: 500 }}>
Slides left and right
</div>
{/if}Animates the opacity and scale of an element. in transitions animate from the provided values to an element's default values. out transitions animate from an element's default values to the provided values.
function scale(
node: Element,
params?: ScaleParams
): TransitionConfigParameters:
interface ScaleParams {
delay?: number;
duration?: number;
easing?: EasingFunction;
start?: number;
opacity?: number;
}0)400)cubicOut)0)0)Example:
<script>
import { scale } from 'svelte/transition';
let visible = $state(false);
</script>
<button onclick={() => visible = !visible}>
Toggle
</button>
{#if visible}
<div transition:scale>
Scales in and out
</div>
{/if}Scale with custom start value:
<script>
import { scale } from 'svelte/transition';
import { backOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:scale={{ start: 0.5, duration: 500, easing: backOut }}>
Scales from 50% to 100%
</div>
{/if}Animates the stroke of an SVG element, like a snake in a tube. in transitions begin with the path invisible and draw the path to the screen over time. out transitions start in a visible state and gradually erase the path. draw only works with elements that have a getTotalLength method, like <path> and <polyline>.
function draw(
node: SVGElement & { getTotalLength(): number },
params?: DrawParams
): TransitionConfigParameters:
interface DrawParams {
delay?: number;
speed?: number;
duration?: number | ((len: number) => number);
easing?: EasingFunction;
}0)cubicInOut)Example:
<script>
import { draw } from 'svelte/transition';
let visible = $state(false);
</script>
<button onclick={() => visible = !visible}>
Toggle
</button>
{#if visible}
<svg width="100" height="100">
<path
d="M 10 10 L 90 90"
stroke="black"
stroke-width="2"
fill="none"
transition:draw
/>
</svg>
{/if}Using speed instead of duration:
<script>
import { draw } from 'svelte/transition';
</script>
{#if visible}
<svg viewBox="0 0 100 100">
<path
d="M 10 50 Q 50 10, 90 50 T 170 50"
stroke="currentColor"
stroke-width="3"
fill="none"
transition:draw={{ speed: 0.5 }}
/>
</svg>
{/if}Dynamic duration based on path length:
<script>
import { draw } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
</script>
{#if visible}
<svg viewBox="0 0 200 200">
<path
d="M 20 20 L 180 180 M 180 20 L 20 180"
stroke="blue"
stroke-width="4"
fill="none"
transition:draw={{
duration: (len) => len * 5,
easing: quintOut
}}
/>
</svg>
{/if}Creates a pair of transitions called send and receive. When an element is 'sent', it looks for a corresponding element being 'received', and generates a transition that transforms the element to its counterpart's position and fades it out. When an element is 'received', the reverse happens. If there is no counterpart, the fallback transition is used.
function crossfade(
params: CrossfadeParams & {
fallback?: (
node: Element,
params: CrossfadeParams,
intro: boolean
) => TransitionConfig;
}
): [
send: (node: Element, params: CrossfadeParams & { key: any }) => () => TransitionConfig,
receive: (node: Element, params: CrossfadeParams & { key: any }) => () => TransitionConfig
]Parameters:
interface CrossfadeParams {
delay?: number;
duration?: number | ((len: number) => number);
easing?: EasingFunction;
}0)cubicOut)Example:
<script>
import { crossfade } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
const [send, receive] = crossfade({
duration: 300,
easing: quintOut
});
let todos = $state([
{ id: 1, text: 'Buy milk', done: false },
{ id: 2, text: 'Walk dog', done: false },
{ id: 3, text: 'Write code', done: true }
]);
function toggle(todo) {
todo.done = !todo.done;
}
</script>
<div class="board">
<div class="column">
<h2>Todo</h2>
{#each todos.filter(t => !t.done) as todo (todo.id)}
<button
onclick={() => toggle(todo)}
in:receive={{ key: todo.id }}
out:send={{ key: todo.id }}
>
{todo.text}
</button>
{/each}
</div>
<div class="column">
<h2>Done</h2>
{#each todos.filter(t => t.done) as todo (todo.id)}
<button
onclick={() => toggle(todo)}
in:receive={{ key: todo.id }}
out:send={{ key: todo.id }}
>
{todo.text}
</button>
{/each}
</div>
</div>With fallback transition:
<script>
import { crossfade, fade } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
const [send, receive] = crossfade({
duration: (d) => Math.sqrt(d * 200),
easing: cubicOut,
fallback: (node, params) => fade(node, { duration: 200 })
});
</script>
<div class="cards">
{#each items as item (item.id)}
<div
in:receive={{ key: item.id }}
out:send={{ key: item.id }}
>
{item.name}
</div>
{/each}
</div>Configuration object returned by transition functions.
interface TransitionConfig {
delay?: number;
duration?: number;
easing?: EasingFunction;
css?: (t: number, u: number) => string;
tick?: (t: number, u: number) => void;
}t (0-1) to eased valuet (where u = 1 - t)Custom transition example:
<script>
import { cubicOut } from 'svelte/easing';
function spin(node, { duration = 400 }) {
return {
duration,
easing: cubicOut,
css: (t, u) => `
transform: rotate(${t * 360}deg);
opacity: ${t};
`
};
}
</script>
{#if visible}
<div transition:spin={{ duration: 800 }}>
Spinning content
</div>
{/if}Using tick for JavaScript animations:
<script>
function typewriter(node, { speed = 1 }) {
const text = node.textContent;
const duration = text.length / (speed * 0.01);
return {
duration,
tick: (t) => {
const i = Math.trunc(text.length * t);
node.textContent = text.slice(0, i);
}
};
}
</script>
{#if visible}
<p transition:typewriter={{ speed: 2 }}>
The quick brown fox jumps over the lazy dog
</p>
{/if}Type for easing functions.
type EasingFunction = (t: number) => number;An easing function takes a value t between 0 and 1 and returns a mapped value, also typically between 0 and 1 (though it can exceed these bounds for effects like overshoot).
The animate module provides the FLIP animation technique for smooth list reordering.
The flip function calculates the start and end position of an element and animates between them, translating the x and y values. FLIP stands for First, Last, Invert, Play.
function flip(
node: Element,
{ from, to }: { from: DOMRect; to: DOMRect },
params?: FlipParams
): AnimationConfigParameters:
interface FlipParams {
delay?: number;
duration?: number | ((len: number) => number);
easing?: (t: number) => number;
}0)d => Math.sqrt(d) * 120)cubicOut)Example:
<script>
import { flip } from 'svelte/animate';
import { quintOut } from 'svelte/easing';
let list = $state([1, 2, 3, 4, 5]);
function shuffle() {
list = list.sort(() => Math.random() - 0.5);
}
</script>
<button onclick={shuffle}>Shuffle</button>
<div class="list">
{#each list as item (item)}
<div animate:flip={{ duration: 500, easing: quintOut }}>
{item}
</div>
{/each}
</div>Combining with transitions:
<script>
import { flip } from 'svelte/animate';
import { fade } from 'svelte/transition';
let items = $state([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' }
]);
function remove(id) {
items = items.filter(item => item.id !== id);
}
function add() {
const id = Math.max(...items.map(i => i.id)) + 1;
items = [...items, { id, name: `Item ${id}` }];
}
</script>
<button onclick={add}>Add Item</button>
<ul>
{#each items as item (item.id)}
<li
animate:flip={{ duration: 300 }}
transition:fade
>
{item.name}
<button onclick={() => remove(item.id)}>×</button>
</li>
{/each}
</ul>Dynamic duration based on distance:
<script>
import { flip } from 'svelte/animate';
import { cubicOut } from 'svelte/easing';
let todos = $state([
{ id: 1, text: 'First todo', priority: 1 },
{ id: 2, text: 'Second todo', priority: 2 },
{ id: 3, text: 'Third todo', priority: 3 }
]);
function sortByPriority() {
todos = todos.sort((a, b) => a.priority - b.priority);
}
</script>
<button onclick={sortByPriority}>Sort</button>
<div class="todos">
{#each todos as todo (todo.id)}
<div
animate:flip={{
duration: (d) => Math.sqrt(d * 500),
easing: cubicOut
}}
>
{todo.text}
</div>
{/each}
</div>Configuration returned by animation functions.
interface AnimationConfig {
delay?: number;
duration?: number;
easing?: (t: number) => number;
css?: (t: number, u: number) => string;
tick?: (t: number, u: number) => void;
}t (0-1) to eased valuet (where u = 1 - t)Custom animation example:
<script>
import { quintOut } from 'svelte/easing';
function customFlip(node, { from, to }, params) {
const dx = from.left - to.left;
const dy = from.top - to.top;
const d = Math.sqrt(dx * dx + dy * dy);
return {
delay: params?.delay || 0,
duration: params?.duration || Math.sqrt(d) * 120,
easing: params?.easing || quintOut,
css: (t, u) => `
transform: translate(${u * dx}px, ${u * dy}px) rotate(${u * 360}deg);
`
};
}
</script>
<div class="grid">
{#each items as item (item.id)}
<div animate:customFlip>
{item.name}
</div>
{/each}
</div>The easing module provides 33 easing functions for controlling animation timing curves.
All easing functions have the same signature:
type EasingFunction = (t: number) => number;They take a value t between 0 and 1 representing progress through the animation and return the eased value.
No easing, linear interpolation.
function linear(t: number): numberExample:
<script>
import { fade } from 'svelte/transition';
import { linear } from 'svelte/easing';
</script>
{#if visible}
<div transition:fade={{ duration: 400, easing: linear }}>
Fades at constant speed
</div>
{/if}Overshoots the target value then settles back.
function backIn(t: number): numberAccelerating from zero velocity with overshoot.
function backOut(t: number): numberDecelerating to zero velocity with overshoot.
function backInOut(t: number): numberAcceleration with overshoot until halfway, then deceleration with overshoot.
Example:
<script>
import { scale } from 'svelte/transition';
import { backOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:scale={{ duration: 500, easing: backOut }}>
Scales with overshoot
</div>
{/if}Bouncing effect like a ball hitting the ground.
function bounceIn(t: number): numberBouncing effect accelerating from zero velocity.
function bounceOut(t: number): numberBouncing effect decelerating to zero velocity.
function bounceInOut(t: number): numberBouncing effect accelerating until halfway, then decelerating.
Example:
<script>
import { fly } from 'svelte/transition';
import { bounceOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:fly={{ y: -200, duration: 800, easing: bounceOut }}>
Bounces down from above
</div>
{/if}Circular acceleration curves.
function circIn(t: number): numberAccelerating from zero velocity using circular function.
function circOut(t: number): numberDecelerating to zero velocity using circular function.
function circInOut(t: number): numberCircular acceleration until halfway, then circular deceleration.
Example:
<script>
import { slide } from 'svelte/transition';
import { circInOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:slide={{ duration: 400, easing: circInOut }}>
Smooth circular motion
</div>
{/if}Cubic (power of 3) acceleration curves.
function cubicIn(t: number): numberAccelerating from zero velocity using cubic function.
function cubicOut(t: number): numberDecelerating to zero velocity using cubic function.
function cubicInOut(t: number): numberCubic acceleration until halfway, then cubic deceleration.
Example:
<script>
import { fade } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:fade={{ duration: 300, easing: cubicOut }}>
Smooth cubic fade
</div>
{/if}Spring/elastic oscillation effect.
function elasticIn(t: number): numberAccelerating from zero velocity with elastic oscillation.
function elasticOut(t: number): numberDecelerating to zero velocity with elastic oscillation.
function elasticInOut(t: number): numberElastic acceleration until halfway, then elastic deceleration.
Example:
<script>
import { scale } from 'svelte/transition';
import { elasticOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:scale={{ duration: 800, easing: elasticOut }}>
Bouncy spring effect
</div>
{/if}Exponential (power of 2) acceleration curves.
function expoIn(t: number): numberAccelerating from zero velocity using exponential function.
function expoOut(t: number): numberDecelerating to zero velocity using exponential function.
function expoInOut(t: number): numberExponential acceleration until halfway, then exponential deceleration.
Example:
<script>
import { fly } from 'svelte/transition';
import { expoOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:fly={{ y: 100, duration: 500, easing: expoOut }}>
Fast exponential deceleration
</div>
{/if}Quadratic (power of 2) acceleration curves.
function quadIn(t: number): numberAccelerating from zero velocity using quadratic function.
function quadOut(t: number): numberDecelerating to zero velocity using quadratic function.
function quadInOut(t: number): numberQuadratic acceleration until halfway, then quadratic deceleration.
Example:
<script>
import { slide } from 'svelte/transition';
import { quadOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:slide={{ duration: 350, easing: quadOut }}>
Gentle quadratic motion
</div>
{/if}Quartic (power of 4) acceleration curves.
function quartIn(t: number): numberAccelerating from zero velocity using quartic function.
function quartOut(t: number): numberDecelerating to zero velocity using quartic function.
function quartInOut(t: number): numberQuartic acceleration until halfway, then quartic deceleration.
Example:
<script>
import { blur } from 'svelte/transition';
import { quartOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:blur={{ duration: 600, easing: quartOut }}>
Strong deceleration curve
</div>
{/if}Quintic (power of 5) acceleration curves - the strongest standard easing curves.
function quintIn(t: number): numberAccelerating from zero velocity using quintic function.
function quintOut(t: number): numberDecelerating to zero velocity using quintic function.
function quintInOut(t: number): numberQuintic acceleration until halfway, then quintic deceleration.
Example:
<script>
import { scale } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:scale={{ duration: 500, easing: quintOut }}>
Very strong deceleration
</div>
{/if}Sinusoidal acceleration curves - gentle and natural motion.
function sineIn(t: number): numberAccelerating from zero velocity using sine function.
function sineOut(t: number): numberDecelerating to zero velocity using sine function.
function sineInOut(t: number): numberSine acceleration until halfway, then sine deceleration.
Example:
<script>
import { fade } from 'svelte/transition';
import { sineInOut } from 'svelte/easing';
</script>
{#if visible}
<div transition:fade={{ duration: 400, easing: sineInOut }}>
Natural, gentle motion
</div>
{/if}Transitions fire events that you can listen to:
<script>
import { fade } from 'svelte/transition';
let visible = $state(false);
function onIntroStart() {
console.log('Intro starting');
}
function onIntroEnd() {
console.log('Intro complete');
}
function onOutroStart() {
console.log('Outro starting');
}
function onOutroEnd() {
console.log('Outro complete');
}
</script>
{#if visible}
<div
transition:fade
onintrostart={onIntroStart}
onintroend={onIntroEnd}
onoutrostart={onOutroStart}
onoutroend={onOutroEnd}
>
Content with event listeners
</div>
{/if}Mark transitions as global to prevent them from being blocked by parent elements:
<script>
import { fade } from 'svelte/transition';
</script>
{#if show}
<div transition:fade|global>
Plays even if parent is transitioning
</div>
{/if}By default, transitions are local (only play when the block they're in is created/destroyed):
<script>
import { slide } from 'svelte/transition';
let showList = $state(true);
let items = $state(['a', 'b', 'c']);
</script>
<button onclick={() => showList = !showList}>
Toggle list
</button>
{#if showList}
<ul>
{#each items as item}
<li transition:slide|local>{item}</li>
{/each}
</ul>
{/if}Defer transitions until all other transitions complete:
<script>
import { fade, fly } from 'svelte/transition';
</script>
<div>
{#if visible}
<div in:fly={{ y: 50 }} out:fade>
First element
</div>
{/if}
{#if visible}
<div in:fly={{ y: 50, delay: 100 }} out:fade|deferred>
Second element (deferred outro)
</div>
{/if}
</div>Use both transitions (for entering/leaving) and animations (for reordering):
<script>
import { fade, fly } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { quintOut } from 'svelte/easing';
let items = $state([1, 2, 3, 4, 5]);
function shuffle() {
items = items.sort(() => Math.random() - 0.5);
}
function remove(id) {
items = items.filter(i => i !== id);
}
</script>
<button onclick={shuffle}>Shuffle</button>
<div class="list">
{#each items as item (item)}
<div
animate:flip={{ duration: 300, easing: quintOut }}
in:fly={{ x: -100 }}
out:fade
>
{item}
<button onclick={() => remove(item)}>×</button>
</div>
{/each}
</div>Create reusable custom transitions:
<script>
import { cubicOut } from 'svelte/easing';
function whoosh(node, params = {}) {
const existingTransform = getComputedStyle(node).transform;
const isMatrixTransform = existingTransform?.startsWith('matrix');
return {
delay: params.delay || 0,
duration: params.duration || 400,
easing: params.easing || cubicOut,
css: (t, u) => {
const rotation = u * 360;
const scale = t;
const x = u * 200;
return `
transform: ${isMatrixTransform ? existingTransform : ''}
translateX(${x}px)
rotate(${rotation}deg)
scale(${scale});
opacity: ${t};
`;
}
};
}
</script>
{#if visible}
<div transition:whoosh={{ duration: 800 }}>
Custom whoosh effect
</div>
{/if}Respect user preferences for reduced motion:
<script>
import { fade, fly } from 'svelte/transition';
import { prefersReducedMotion } from 'svelte/motion';
let visible = $state(false);
$effect(() => {
if (prefersReducedMotion.current) {
console.log('User prefers reduced motion');
}
});
</script>
{#if visible}
<div transition:fly={{
y: prefersReducedMotion.current ? 0 : 200,
duration: prefersReducedMotion.current ? 0 : 400
}}>
Respects motion preferences
</div>
{/if}Coordinate transitions across multiple elements:
<script>
import { fade, fly, slide } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
let step = $state(1);
</script>
<div class="wizard">
{#if step === 1}
<div
in:fly={{ x: 300, duration: 300, easing: quintOut }}
out:fly={{ x: -300, duration: 300, easing: quintOut }}
>
<h2>Step 1</h2>
<button onclick={() => step = 2}>Next</button>
</div>
{:else if step === 2}
<div
in:fly={{ x: 300, duration: 300, easing: quintOut }}
out:fly={{ x: -300, duration: 300, easing: quintOut }}
>
<h2>Step 2</h2>
<button onclick={() => step = 1}>Back</button>
<button onclick={() => step = 3}>Next</button>
</div>
{:else}
<div
in:fly={{ x: 300, duration: 300, easing: quintOut }}
out:fly={{ x: -300, duration: 300, easing: quintOut }}
>
<h2>Step 3</h2>
<button onclick={() => step = 2}>Back</button>
</div>
{/if}
</div>Prefer CSS-based transitions when possible for better performance:
<script>
// Good: CSS-based (hardware accelerated)
function fade(node, params) {
return {
duration: params.duration || 400,
css: t => `opacity: ${t}`
};
}
// Avoid: JavaScript-based (runs on main thread)
function fadeTick(node, params) {
return {
duration: params.duration || 400,
tick: t => {
node.style.opacity = t;
}
};
}
</script>Use transform and opacity for smooth 60fps animations:
<script>
import { cubicOut } from 'svelte/easing';
// Good: uses transform (hardware accelerated)
function slideOptimized(node, params) {
const height = node.offsetHeight;
return {
duration: params.duration || 400,
easing: cubicOut,
css: t => `
transform: translateY(${(1 - t) * -height}px);
opacity: ${t};
`
};
}
// Avoid: animates height (causes reflow)
function slideUnoptimized(node, params) {
const height = node.offsetHeight;
return {
duration: params.duration || 400,
css: t => `
height: ${t * height}px;
`
};
}
</script>Add will-change hints for complex animations:
<script>
function optimizedTransition(node, params) {
// Set will-change before transition starts
node.style.willChange = 'transform, opacity';
return {
duration: params.duration || 400,
css: t => `
transform: scale(${t});
opacity: ${t};
`,
// Clean up after transition ends
tick: (t) => {
if (t === 1) {
node.style.willChange = 'auto';
}
}
};
}
</script>out easings for entering, in easings for leavingprefersReducedMotion for accessibilityanimate:flip with transitions for smooth list updates