Skip to main content
The animate: directive triggers animations when elements are reordered within a keyed each block. Unlike transitions, animations are triggered by position changes, not element addition or removal.

Basic Usage

Animations must be used on immediate children of a keyed each block:
<script>
  import { flip } from 'svelte/animate';

  let items = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' }
  ];

  function reorder() {
    items = items.reverse();
  }
</script>

<button on:click={reorder}>Reverse</button>

{#each items as item (item.id)}
  <div animate:flip>
    {item.name}
  </div>
{/each}

When Animations Trigger

Animations run when:
  • An element’s position changes within a keyed each block
  • The list is reordered or sorted
  • Items are rearranged in the data array
Animations do not run when:
  • Elements are added to the list (use in: transitions)
  • Elements are removed from the list (use out: transitions)
  • The each block is not keyed

With Parameters

Pass parameters to customize the animation behavior:
{#each list as item (item.id)}
  <li animate:flip={{ delay: 200, duration: 400 }}>
    {item.text}
  </li>
{/each}
The double curly braces {{}} represent an object literal inside an expression tag.

Built-in Animation

Svelte provides one built-in animation function:
  • flip - Smoothly animates position changes using the FLIP technique
import { flip } from 'svelte/animate';

Custom Animation Functions

Create custom animations by providing a function that returns an animation configuration:
<script>
  import { cubicOut } from 'svelte/easing';

  /**
   * @param {HTMLElement} node - The element being animated
   * @param {{ from: DOMRect; to: DOMRect }} positions - Start and end positions
   * @param {any} params - Custom parameters
   */
  function whizz(node, { from, to }, params) {
    const dx = from.left - to.left;
    const dy = from.top - to.top;
    const distance = Math.sqrt(dx * dx + dy * dy);

    return {
      delay: 0,
      duration: Math.sqrt(distance) * 120,
      easing: cubicOut,
      css: (t, u) => {
        return `transform: translate(${ u * dx }px, ${ u * dy }px) rotate(${ t * 360 }deg);`;
      }
    };
  }
</script>

{#each list as item (item.id)}
  <div animate:whizz>{item}</div>
{/each}

Animation Function Signature

type AnimationFn = (
  node: HTMLElement,
  { from: DOMRect; to: DOMRect },
  params: any
) => AnimationConfig;

Animation Configuration

Your animation function should return an object with these properties:
delay
number
default:"0"
Milliseconds before the animation starts
duration
number
default:"300"
Duration of the animation in milliseconds
easing
(t: number) => number
default:"linear"
An easing function that transforms the animation timeline
css
(t: number, u: number) => string
A function that returns CSS to apply at each frame. t is a value from 0-1 after easing is applied, u equals 1 - t. If provided, Svelte creates a web animation that runs off the main thread for better performance.
tick
(t: number, u: number) => void
A function called on each frame with t and u values. Use this when you need to imperatively update the DOM, but prefer css when possible as it performs better.

Position Information

The animation function receives from and to parameters containing DOMRect objects:
  • from - The element’s bounding rectangle before reordering
  • to - The element’s bounding rectangle after reordering
These provide properties like left, top, width, height, and more.

Animation Timeline

The t and u parameters in css and tick functions represent the animation progress:
  • t - Progress from 0 to 1, with easing applied
  • u - Equals 1 - t, useful for inverting values
The function is called repeatedly before and during the animation with different values.

Using css vs tick

Prefer the css option when possible:
<script>
  function slideAndFade(node, { from, to }, params) {
    const dx = from.left - to.left;
    
    return {
      duration: 300,
      css: (t, u) => `
        transform: translateX(${ u * dx }px);
        opacity: ${ t };
      `
    };
  }
</script>
Use tick only when CSS animations aren’t sufficient:
<script>
  function customAnimation(node, { from, to }, params) {
    return {
      duration: 300,
      tick: (t, u) => {
        // Imperative DOM updates
        node.style.color = t > 0.5 ? 'blue' : 'red';
      }
    };
  }
</script>
Web animations created with css can run off the main thread, preventing jank on slower devices. Use tick only when necessary.

Complete Example

Here’s a sortable todo list with animations:
<script>
  import { flip } from 'svelte/animate';
  import { fade } from 'svelte/transition';

  let todos = [
    { id: 1, done: false, text: 'Write docs' },
    { id: 2, done: false, text: 'Build feature' },
    { id: 3, done: true, text: 'Fix bug' },
    { id: 4, done: false, text: 'Review PR' }
  ];

  function sort() {
    todos = todos.sort((a, b) => a.done - b.done);
  }

  function remove(id) {
    todos = todos.filter(t => t.id !== id);
  }
</script>

<button on:click={sort}>Sort by status</button>

<ul>
  {#each todos as todo (todo.id)}
    <li 
      animate:flip={{ duration: 300 }}
      out:fade={{ duration: 200 }}
    >
      <input type="checkbox" bind:checked={todo.done} />
      <span>{todo.text}</span>
      <button on:click={() => remove(todo.id)}></button>
    </li>
  {/each}
</ul>

TypeScript

// Animation directive usage
interface AnimationDirective {
  (node: Element, fn: AnimationFn, params?: any): void;
}

// Animation function type
type AnimationFn = (
  node: HTMLElement,
  positions: { from: DOMRect; to: DOMRect },
  params: any
) => AnimationConfig;

// Return type
interface AnimationConfig {
  delay?: number;
  duration?: number;
  easing?: (t: number) => number;
  css?: (t: number, u: number) => string;
  tick?: (t: number, u: number) => void;
}