Svelte Transitions and Animations
Add enter/exit animations, list reordering motion, and spring physics to Svelte elements using built-in and custom transitions
When to Use
- You need elements to animate in/out when they enter or leave the DOM based on conditional rendering
- You want to animate list reordering when items move within an
{#each}block - You need smooth value transitions (progress bars, counters) using tweened or spring stores
- You are building custom animation functions for reusable transition effects
Instructions
Built-in transitions:
- Import and apply transitions from
svelte/transition. They run when the element is conditionally rendered:
<script>
import { fade, fly, slide, scale, blur } from 'svelte/transition'
let show = $state(true)
</script>
<button => show = !show}>Toggle</button>
{#if show}
<div transition:fade>Fades in and out</div>
<div transition:fly={{ y: 20, duration: 300 }}>Flies from below</div>
<div transition:slide>Slides open/closed</div>
{/if}
- Use
in:andout:directives to apply different transitions for enter and exit:
{#if show}
<div in:fly={{ x: -100 }} out:fade>
Flies in from left, fades out
</div>
{/if}
- Pass parameters to customize built-in transitions:
<div transition:fly={{ x: 0, y: 50, duration: 400, easing: quintOut }}>
Parameterized fly
</div>
Import easing functions from svelte/easing: linear, cubicIn, cubicOut, cubicInOut, quintOut, elasticOut, backOut, etc.
Custom transitions:
- Write a custom transition function that returns CSS keyframes or a tick function:
// CSS-based custom transition (most performant)
function swoosh(node: Element, { duration = 300 } = {}) {
return {
duration,
css: (t: number) => `
transform: translateX(${(1 - t) * 100}%) rotate(${(1 - t) * 45}deg);
opacity: ${t};
`,
};
}
// JS-based tick function (for canvas or non-CSS animations)
function typewriter(node: Element, { speed = 40 } = {}) {
const text = node.textContent ?? '';
return {
duration: text.length * speed,
tick: (t: number) => {
node.textContent = text.slice(0, Math.floor(t * text.length));
},
};
}
Animating list items with animate:flip:
- Use
animate:flipto smoothly reorder elements in{#each}blocks when items move:
<script>
import { flip } from 'svelte/animate'
import { fade } from 'svelte/transition'
let items = $state(['A', 'B', 'C', 'D'])
function shuffle() {
items = [...items].sort(() => Math.random() - 0.5)
}
</script>
<button
{#each items as item (item)}
<div animate:flip={{ duration: 300 }} transition:fade>
{item}
</div>
{/each}
Note: (item) — the key expression — is required for animate:flip to track element identity.
Tweened and spring stores for smooth value transitions:
- Use
tweenedfor smooth numeric value interpolation (progress bars, counters):
<script>
import { tweened } from 'svelte/motion'
import { cubicOut } from 'svelte/easing'
const progress = tweened(0, { duration: 500, easing: cubicOut })
function complete() { progress.set(1) }
</script>
<progress value={$progress} />
<button
- Use
springfor physics-based motion that overshoots and settles:
<script>
import { spring } from 'svelte/motion'
const pos = spring({ x: 0, y: 0 }, { stiffness: 0.1, damping: 0.25 })
</script>
<div
style="transform: translate({$pos.x}px, {$pos.y}px)"
=> pos.set({ x: e.clientX, y: e.clientY })}
/>
Deferred transitions (crossfade):
- Use
crossfadeto animate an element from one position to another across the DOM (shared element transition):
<script>
import { crossfade } from 'svelte/transition'
const [send, receive] = crossfade({ duration: 400 })
</script>
{#if inList}
<div in:receive={{ key: item.id }} out:send={{ key: item.id }}>
{item.name}
</div>
{:else}
<div in:receive={{ key: item.id }} out:send={{ key: item.id }}>
{item.name}
</div>
{/if}
Details
How transitions work:
Svelte compiles transition:, in:, and out: directives into JavaScript that runs when the element is added to or removed from the DOM. CSS-based transitions run entirely on the compositor thread and do not block the main thread.
global modifier:
By default, transitions only run when the direct parent {#if} changes. Add :global to run the transition when any ancestor condition changes:
{#if outerCondition}
{#if innerCondition}
<div transition:fade|global>
Fades when either condition changes
</div>
{/if}
{/if}
Transition events:
Listen to transition lifecycle events:
<div
transition:fly
=> console.log('entering')}
=> console.log('entered')}
=> console.log('leaving')}
=> console.log('left')}
/>
Performance considerations:
- Prefer CSS-based custom transitions (return
css) over JS tick functions — CSS runs off the main thread - Avoid animating
width/height— usetransform: scaleX()andtransform: scaleY()instead slideanimates height and may cause layout thrash on complex elements; preferscaleor custom transitions for performance-sensitive cases
Source
https://svelte.dev/docs/svelte/transition
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.