This episode brings motion to your interfaces: built-in transitions, the motion directives transition, in, out, and animate, custom animation with tweened and spring, and how to use motion as part of a mature UI and UX strategy.

So far you have been building reactive, communicative interfaces. Episode 7 adds the dimension that makes an application feel alive: motion. Elements that appear, disappear, and move smoothly are not just pretty — they give users context about what is happening.
Svelte provides a unique built-in motion system: you declare transition:, in:, out:, and animate: directly in the markup, and the compiler handles the rest. No additional animation library is needed for common needs, and tweened plus spring give you fine control over value-based animation.
In this episode you will learn built-in transitions, motion directives, custom animation with tweened and spring, and the principles of using motion responsibly in UX design.
Svelte includes ready-to-use transition functions: fade, fly, slide, scale, blur, and draw. Import them from svelte/transition and attach them to elements:
<script>
import { fade, slide } from "svelte/transition"
let tampil = $state(true)
</script>
{#if tampil}
<p transition:slide>Elemen ini meluncur masuk dan keluar</p>
<p transition:fade={{ duration: 500 }}>Elemen dengan durasi khusus</p>
{/if}
<button onclick={() => (tampil = !tampil)}>Toggle</button>transition:slide makes the element slide in and out. Parameter objects like { duration: 500 } are passed through the attribute — here transition:fade={{ duration: 500 }}. The outgoing motion automatically reuses the same transition; you do not need to write it twice.
Sometimes you want different in and out motions. Use in: and out: separately:
<script>
import { fly, scale } from "svelte/transition"
let tampil = $state(false)
</script>
{#if tampil}
<div in:fly={{ y: 20, duration: 300 }} out:scale>
Panel muncul melayang, hilang menyusut
</div>
{/if}
<button onclick={() => (tampil = !tampil)}>Buka panel</button>in:fly={{ y: 20, duration: 300 }} moves the element up from 20 pixels below as it appears, while out:scale shrinks it as it disappears. Separating in: and out: gives you full control over the entrance and exit choreography of elements.
When items in an #each change position, Svelte can animate the movement with animate: — provided the list uses a key:
<script>
import { flip } from "svelte/animate"
let daftar = $state([1, 2, 3, 4])
function acak() {
daftar = daftar.toSorted(() => Math.random() - 0.5)
}
</script>
{#each daftar as n (n)}
<div animate:flip>{n}</div>
{/each}
<button onclick={acak}>Acak urutan</button>animate:flip applies FLIP animation: items that change position are animated smoothly from their old position to the new one. The key (n) in #each is required so Svelte can track each item's identity. The result feels magical: a list that rearranges itself with graceful motion.
Items moving without animation feel jarring — users struggle to follow where an item went. animate: solves this with a single attribute and no external library. It is a perfect example of the Svelte philosophy: complex work, minimal syntax.
tweened and spring are stores from svelte/motion whose value changes run smoothly. tweened moves with a configurable duration and easing:
<script>
import { tweened } from "svelte/motion"
import { cubicOut } from "svelte/easing"
const nilai = tweened(0, { duration: 800, easing: cubicOut })
</script>
<button onclick={() => nilai.set(100)}>Naik ke 100</button>
<p>{$nilai}</p>tweened(0, { duration: 800, easing: cubicOut }) creates a store whose value animates from the old value to the new one over 800 milliseconds. Under the hood this is a readable store — which is why it is used with $nilai in the template. Great for progress bars, statistic numbers, and linearly moving values.
spring mimics physics with stiffness and damping parameters — not a fixed duration, but a speed that changes with distance. This produces motion that feels natural:
<script>
import { spring } from "svelte/motion"
const posisi = spring({ x: 50, y: 50 }, { stiffness: 0.1, damping: 0.25 })
</script>
<div
style="transform: translate({$posisi.x}px, {$posisi.y}px)"
onmousemove={(e) => posisi.set({ x: e.clientX, y: e.clientY })}
>
Lingkaran mengikuti kursor
</div>spring({ x: 50, y: 50 }, ...) stores the position as an object. When a new value is set, the spring accelerates or decelerates toward the target with an elastic effect. The stiffness and damping values determine how fast it moves and how much it oscillates — play with both parameters for the feel you want.
Good motion guides attention; it does not steal it. A few practical guidelines:
Accessibility is non-negotiable. Detect user preferences with a CSS media query and disable heavy animation:
<script>
import { prefersReducedMotion } from "svelte/motion"
</script>
<p>Pengguna menonaktifkan animasi: {$prefersReducedMotion}</p>prefersReducedMotion is a built-in Svelte 5 store that reads the prefers-reduced-motion media query from the user's operating system. Use its value to choose between full transitions and minimal motion — a small courtesy with a big impact for users with vestibular disorders.
Key takeaways:
fade, fly, and slide work out of the box with no external library.in: and out: separate entering and exiting motion; transition: uses one motion for both.animate:flip animates reordered list items, provided #each has a key.tweened suits linear values with duration and easing; spring for natural physics-based motion.prefers-reduced-motion with the prefersReducedMotion store.In the next episode 8 we will discuss data fetching and async — fetching data with fetch and async/await, reactive data loading and error handling, SvelteKit load functions on the server side, and simple caching and request state. The animations from this episode will decorate the results of your fetches later on.