Transitions and animations
A transition is an animation that plays when an element appears on the page or disappears from it. In Svelte the built-in transitions are added with one directive, without CSS keyframes or separate libraries.
The transition directive
Built-in transitions live in the svelte/transition module. The simplest is fade, a smooth appearance and disappearance:
<!-- src/routes/+page.svelte -->
<script>
import { fade } from 'svelte/transition';
let showHint = $state(true);
</script>
<button onclick={() => (showHint = !showHint)}>Hint</button>
{#if showHint}
<p transition:fade>Mark finished tasks with a checkmark.</p>
{/if}The button hides and shows the paragraph, and each time it smoothly fades out or in. A transition plays when an element is added or removed by an {#if} or {#each} block.
Parameters
A transition can take a settings object:
<script>
import { fly } from 'svelte/transition';
let showHint = $state(true);
</script>
{#if showHint}
<p transition:fly={{ y: -20, duration: 300 }}>A new hint</p>
{/if}The paragraph flies in from 20 pixels above over 300 milliseconds. The double curly braces are an object inside an expression.
| Transition | What it does |
|---|---|
fade | changes opacity |
fly | moves and changes opacity |
slide | expands vertically |
scale | grows or shrinks |
Different transitions in and out
The in: and out: directives set transitions separately for appearing and disappearing:
<!-- src/routes/+page.svelte: a row of the to-do list -->
<script>
import { fly, fade } from 'svelte/transition';
let todos = $state([{ id: 1, text: 'Buy bread' }]);
</script>
<ul>
{#each todos as todo (todo.id)}
<li in:fly={{ x: -30 }} out:fade>{todo.text}</li>
{/each}
</ul>A new task slides in from the left, and a deleted one quietly fades away.
Reorder animation
When list items change order, the flip function from the svelte/animate module moves them smoothly to their new places. It works only in a keyed {#each} block:
<script>
import { flip } from 'svelte/animate';
let todos = $state([
{ id: 1, text: 'Buy bread' },
{ id: 2, text: 'Call Oleg' }
]);
</script>
<button onclick={() => todos.reverse()}>Reverse</button>
<ul>
{#each todos as todo (todo.id)}
<li animate:flip={{ duration: 300 }}>{todo.text}</li>
{/each}
</ul>After the click the rows do not jump but smoothly swap places.
Common mistake
Putting transition: on an element that is always on the page and expecting an animation when its text changes. A transition plays only when an element is added or removed. For it to run, wrap the element in {#if} or {#each}.