Svelte transitions – tried all 7 built-in ones
TL;DR
Svelte has 7 built-in transitions (fade, blur, fly, slide, scale, draw, crossfade). I tried each one with short examples and GIFs. Use this as a quick reference when you need a transition.
Intro
While working on a Svelte project, I learned that Svelte has its own transition syntax. I tried all seven built-in transitions and noted how each behaves.
- Docs: Template syntax, Reference
The transitions
1. fade
<button onclick={() => (visibleFade = !visibleFade)}>Toggle</button>
{#if visibleFade}
<div transition:fade={{ duration: 300 }}>Fade</div>
{/if}
2. blur
<button onclick={() => (visibleBlur = !visibleBlur)}>Toggle</button>
{#if visibleBlur}
<div transition:blur={{ duration: 400, amount: 8 }}>Blur</div>
{/if}
3. fly
<button onclick={() => (visibleFly = !visibleFly)}>Toggle</button>
{#if visibleFly}
<div transition:fly={{ y: 20, duration: 400 }}>Fly</div>
{/if}
4. slide
<button onclick={() => (visibleSlide = !visibleSlide)}>Toggle</button>
{#if visibleSlide}
<div transition:slide={{ duration: 400, axis: 'y' }}>Slide</div>
{/if}
5. scale
<button onclick={() => (visibleScale = !visibleScale)}>Toggle</button>
{#if visibleScale}
<div transition:scale={{ duration: 400, start: 0, opacity: 0.5 }}>Scale</div>
{/if}
6. draw
<button onclick={() => (visibleDraw = !visibleDraw)}>Toggle</button>
<svg viewBox="0 0 100 40" width="120" height="48" aria-hidden="true">
{#if visibleDraw}
<path
transition:draw={{ duration: 800 }}
fill="none"
stroke="var(--color-accent, #89b4fa)"
stroke-width="2"
stroke-linecap="round"
d="M 10 20 Q 50 5 90 20"
/>
{/if}
</svg>
7. crossfade
As the docs say:
The crossfade function creates a pair of transitions called send and receive.
So crossfade is different: it works with two elements (one leaving, one entering). Use the same key on both so Svelte treats them as the same content moving. Fallback is used when there’s no pair (e.g. first load); here we use fade so it still fades in/out instead of popping.
<script>
const [send, receive] = crossfade({
duration: 400,
fallback: (node, _params, intro) => fade(node, { duration: 400 })
});
</script>
<button onclick={() => (crossfadePosition = crossfadePosition === 0 ? 1 : 0)}>
Move left ↔ right
</button>
<div>
<div>
{#if crossfadePosition === 0}
<div in:receive={{ key: 'same' }} out:send={{ key: 'same' }}>
Moves
</div>
{/if}
</div>
<div>
{#if crossfadePosition === 1}
<div in:receive={{ key: 'same' }} out:send={{ key: 'same' }}>
Moves
</div>
{/if}
</div>
</div>
8. Bonus - Custom transition
If you need something custom, you can use the transition contract and build your own.
<script lang="ts">
function spinFade(node: HTMLElement, { duration = 500, delay = 0, turns = 1 }: SpinFadeParams) {
const style = getComputedStyle(node);
const opacity = +style.opacity;
return {
delay,
duration,
css: (t: number) => {
const angle = (1 - t) * turns * 360;
return `opacity: \({t * opacity}; transform: rotate(\){angle}deg);`;
}
};
}
</script>
<button onclick={() => (visibleCustom = !visibleCustom)}>Toggle</button>
{#if visibleCustom}
<div transition:spinFade={{ duration: 500, turns: 1 }}>Custom</div>
{/if}
Quick comparison
| Transition | Best for | Note |
|---|---|---|
| fade | Simple show/hide | Easiest, good default |
| blur | Focus / attention | Use amount to control |
| fly | Element “moves into” the place | x, y, opacity |
| slide | Element “expands/collapses” | axis: 'x' or 'y' |
| scale | Zoom in/out feel | start, opacity |
| draw | SVG paths only | Stroke animation |
| crossfade | Moving content between containers | Needs send / receive |
Conclusion
- fly = element moves in; slide = element grows/shrinks in place.
Easy to wire up and no extra dependency – Probably I’d use them in a real product. For simple show/hide or list changes, the built-ins are enough; if you need something fancy, the custom transition API is still straightforward. Worth trying when you want a bit of polish without much code. 😆


