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

transitions-animations.mddocs/reference/

Svelte Transitions and Animations

Complete API reference for Svelte's built-in transition and animation system.

Overview

Svelte provides a powerful system for creating smooth, performant animations:

  • svelte/transition - Built-in transition effects for elements entering/leaving the DOM
  • svelte/animate - FLIP animations for smooth list reordering
  • svelte/easing - 33 easing functions for controlling animation timing

Module: svelte/transition

The transition module provides functions that create visual effects when elements enter or leave the DOM.

Transition Functions

blur()

Animates a blur filter alongside an element's opacity.

function blur(
  node: Element,
  params?: BlurParams
): TransitionConfig

Parameters:

interface BlurParams {
  delay?: number;
  duration?: number;
  easing?: EasingFunction;
  amount?: number | string;
  opacity?: number;
}
  • delay - Milliseconds before the transition starts (default: 0)
  • duration - Milliseconds the transition lasts (default: 400)
  • easing - Easing function to use (default: cubicInOut)
  • amount - Size of the blur in pixels (default: 5)
  • opacity - Target opacity value, 0-1 (default: 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}

fade()

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
): TransitionConfig

Parameters:

interface FadeParams {
  delay?: number;
  duration?: number;
  easing?: EasingFunction;
}
  • delay - Milliseconds before the transition starts (default: 0)
  • duration - Milliseconds the transition lasts (default: 400)
  • easing - Easing function to use (default: 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}

fly()

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
): TransitionConfig

Parameters:

interface FlyParams {
  delay?: number;
  duration?: number;
  easing?: EasingFunction;
  x?: number | string;
  y?: number | string;
  opacity?: number;
}
  • delay - Milliseconds before the transition starts (default: 0)
  • duration - Milliseconds the transition lasts (default: 400)
  • easing - Easing function to use (default: cubicOut)
  • x - Horizontal offset in pixels or CSS units (default: 0)
  • y - Vertical offset in pixels or CSS units (default: 0)
  • opacity - Target opacity value, 0-1 (default: 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}

slide()

Slides an element in and out.

function slide(
  node: Element,
  params?: SlideParams
): TransitionConfig

Parameters:

interface SlideParams {
  delay?: number;
  duration?: number;
  easing?: EasingFunction;
  axis?: 'x' | 'y';
}
  • delay - Milliseconds before the transition starts (default: 0)
  • duration - Milliseconds the transition lasts (default: 400)
  • easing - Easing function to use (default: cubicOut)
  • axis - Axis of motion, '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}

scale()

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
): TransitionConfig

Parameters:

interface ScaleParams {
  delay?: number;
  duration?: number;
  easing?: EasingFunction;
  start?: number;
  opacity?: number;
}
  • delay - Milliseconds before the transition starts (default: 0)
  • duration - Milliseconds the transition lasts (default: 400)
  • easing - Easing function to use (default: cubicOut)
  • start - Starting scale value, 0-1 (default: 0)
  • opacity - Target opacity value, 0-1 (default: 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}

draw()

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
): TransitionConfig

Parameters:

interface DrawParams {
  delay?: number;
  speed?: number;
  duration?: number | ((len: number) => number);
  easing?: EasingFunction;
}
  • delay - Milliseconds before the transition starts (default: 0)
  • speed - Animation speed in pixels per millisecond (alternative to duration)
  • duration - Milliseconds the transition lasts, or function that receives path length and returns duration
  • easing - Easing function to use (default: 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}

crossfade()

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;
}
  • delay - Milliseconds before the transition starts (default: 0)
  • duration - Milliseconds the transition lasts, or function that receives distance and returns duration
  • easing - Easing function to use (default: cubicOut)
  • fallback - Transition to use when no counterpart element exists

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>

Types

TransitionConfig

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;
}
  • delay - Milliseconds to wait before starting the transition
  • duration - How long the transition lasts in milliseconds
  • easing - Function that maps time t (0-1) to eased value
  • css - Function that returns CSS to apply at time t (where u = 1 - t)
  • tick - Function called on every frame during the transition

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}

EasingFunction

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).

Module: svelte/animate

The animate module provides the FLIP animation technique for smooth list reordering.

Functions

flip()

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
): AnimationConfig

Parameters:

interface FlipParams {
  delay?: number;
  duration?: number | ((len: number) => number);
  easing?: (t: number) => number;
}
  • delay - Milliseconds before the animation starts (default: 0)
  • duration - Milliseconds the animation lasts, or function that receives distance and returns duration (default: d => Math.sqrt(d) * 120)
  • easing - Easing function to use (default: 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>

Types

AnimationConfig

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;
}
  • delay - Milliseconds to wait before starting the animation
  • duration - How long the animation lasts in milliseconds
  • easing - Function that maps time t (0-1) to eased value
  • css - Function that returns CSS to apply at time t (where u = 1 - t)
  • tick - Function called on every frame during the animation

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>

Module: svelte/easing

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.

Easing Functions

linear

No easing, linear interpolation.

function linear(t: number): number

Example:

<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}

Back Easing

Overshoots the target value then settles back.

backIn

function backIn(t: number): number

Accelerating from zero velocity with overshoot.

backOut

function backOut(t: number): number

Decelerating to zero velocity with overshoot.

backInOut

function backInOut(t: number): number

Acceleration 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}

Bounce Easing

Bouncing effect like a ball hitting the ground.

bounceIn

function bounceIn(t: number): number

Bouncing effect accelerating from zero velocity.

bounceOut

function bounceOut(t: number): number

Bouncing effect decelerating to zero velocity.

bounceInOut

function bounceInOut(t: number): number

Bouncing 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 Easing

Circular acceleration curves.

circIn

function circIn(t: number): number

Accelerating from zero velocity using circular function.

circOut

function circOut(t: number): number

Decelerating to zero velocity using circular function.

circInOut

function circInOut(t: number): number

Circular 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 Easing

Cubic (power of 3) acceleration curves.

cubicIn

function cubicIn(t: number): number

Accelerating from zero velocity using cubic function.

cubicOut

function cubicOut(t: number): number

Decelerating to zero velocity using cubic function.

cubicInOut

function cubicInOut(t: number): number

Cubic 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}

Elastic Easing

Spring/elastic oscillation effect.

elasticIn

function elasticIn(t: number): number

Accelerating from zero velocity with elastic oscillation.

elasticOut

function elasticOut(t: number): number

Decelerating to zero velocity with elastic oscillation.

elasticInOut

function elasticInOut(t: number): number

Elastic 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 Easing

Exponential (power of 2) acceleration curves.

expoIn

function expoIn(t: number): number

Accelerating from zero velocity using exponential function.

expoOut

function expoOut(t: number): number

Decelerating to zero velocity using exponential function.

expoInOut

function expoInOut(t: number): number

Exponential 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 Easing

Quadratic (power of 2) acceleration curves.

quadIn

function quadIn(t: number): number

Accelerating from zero velocity using quadratic function.

quadOut

function quadOut(t: number): number

Decelerating to zero velocity using quadratic function.

quadInOut

function quadInOut(t: number): number

Quadratic 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 Easing

Quartic (power of 4) acceleration curves.

quartIn

function quartIn(t: number): number

Accelerating from zero velocity using quartic function.

quartOut

function quartOut(t: number): number

Decelerating to zero velocity using quartic function.

quartInOut

function quartInOut(t: number): number

Quartic 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 Easing

Quintic (power of 5) acceleration curves - the strongest standard easing curves.

quintIn

function quintIn(t: number): number

Accelerating from zero velocity using quintic function.

quintOut

function quintOut(t: number): number

Decelerating to zero velocity using quintic function.

quintInOut

function quintInOut(t: number): number

Quintic 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}

Sine Easing

Sinusoidal acceleration curves - gentle and natural motion.

sineIn

function sineIn(t: number): number

Accelerating from zero velocity using sine function.

sineOut

function sineOut(t: number): number

Decelerating to zero velocity using sine function.

sineInOut

function sineInOut(t: number): number

Sine 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}

Advanced Patterns

Transition Events

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}

Global Transitions

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}

Local Transitions

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}

Deferred Transitions

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>

Combining Transitions and Animations

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>

Custom Transition with Parameters

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}

Accessible Animations

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}

Coordinated Multi-Element Transitions

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>

Performance Considerations

CSS vs JavaScript Transitions

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>

Transform Properties

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>

Will-Change Optimization

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>

Best Practices

  1. Use appropriate durations: 200-400ms for most UI transitions
  2. Choose matching easings: out easings for entering, in easings for leaving
  3. Respect user preferences: Check prefersReducedMotion for accessibility
  4. Combine with FLIP: Use animate:flip with transitions for smooth list updates
  5. Key your each blocks: Always provide keys when using animations
  6. Test performance: Monitor frame rates with complex transition combinations
  7. Progressive enhancement: Ensure UI works without JavaScript
  8. Coordinate related transitions: Use similar durations and easings for related elements

See Also

  • svelte/motion - Spring and Tween for reactive motion
  • Lifecycle Functions - Component lifecycle hooks
  • Reactivity - Svelte's reactivity system