Svelte takes a different approach to building user interfaces: it compiles at build time, producing highly efficient runtime code with minimal boilerplate. In this post you’ll learn four core Svelte concepts that make building interactive UIs expressive and fun: reactive declarations, stores, animations, and transitions. Each section explains what the feature is, why it matters, and includes copy-paste examples you can try right away.
1) Reactive Declarations โ Automatic, predictable updates โก
What they are
Core Terms & Abbreviations โ Quick Glossary ๐ค
- SFC (Single File Component): a
.sveltefile that contains markup, logic, and styles together. - SPA (Single Page Application): an app that runs entirely in the browser and dynamically updates the view.
- SSR (Server-Side Rendering): rendering initial HTML on the server (SvelteKit supports this).
- SSG (Static Site Generation): pre-rendering HTML at build time for fast, cacheable pages.
- HMR (Hot Module Replacement): dev-time feature that updates modules without a full reload. – REPL: Svelte’s online REPL at svelte.dev/repl for quick experiments.
If these terms are new, the Deployment & Resources sections below link to further reading and tools.
Svelte uses the $: label for reactive declarations. Whenever a dependency used inside a $: block changes, Svelte automatically re-runs that statement and updates any bound UI. This is not a runtime Virtual DOM diff โ it’s compiled code that updates only what’s necessary.
Why it matters
Reactive declarations let you derive values declaratively without writing imperative watchers. The code reads like plain JavaScript, which reduces cognitive load and bugs.
Practical example
<script>
let first = 'Ada';
let last = 'Lovelace';
// reactive declaration: runs whenever `first` or `last` changes
$: full = `${first} ${last}`;
</script>
<p>Full name: {full}</p>
<input bind:value={first} placeholder="First" />
<input bind:value={last} placeholder="Last" />
Key takeaway
- Use
$:for derived values and side effects that depend on reactive variables. Prefer pure derivations; for side effects useonMountor stores depending on scope.
Advanced reactive patterns
Reactive declarations are powerful and can be used for more than simple derivations. Two common patterns:
- Reactive statements with conditions โ run code when a value changes:
let count = 0;
$: if (count > 10) {
console.log('count exceeded 10', count);
}
- Reactive statements that call async functions โ be mindful of cancellation and ordering:
import { onDestroy } from 'svelte';
let q = '';
let results = [];
$: if (q) {
const controller = new AbortController();
(async () => {
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, { signal: controller.signal });
results = await res.json();
})();
onDestroy(() => controller.abort());
}
Key point: when using reactive statements for side effects, ensure you handle cancellation and avoid race conditions.
2) Stores โ shared state made simple ๐๏ธ
What they are
Svelte stores are lightweight objects that allow components to share reactive state. There are three main store types:
writable(value): read + write storereadable(start, stop): read-only store that can have a custom subscription lifecyclederived(store, fn): computed store derived from other stores
Why they matter
Stores avoid prop drilling and make it trivial to reactively share state across the component tree while keeping the API small and predictable.
Practical example: a writable store
// src/stores/counter.js
import { writable } from 'svelte/store';
export const counter = writable(0);
<!-- Counter.svelte -->
<script>
import { counter } from './stores/counter.js';
</script>
<button on:click={() => counter.update(n => n + 1)}>Increment</button>
<p>Count: {$counter}</p> <!-- $auto-subscription syntax -->
Derived store example
import { derived } from 'svelte/store';
import { counter } from './counter.js';
export const doubled = derived(counter, $c => $c * 2);
Key takeaway
- Use stores for global-ish state like auth, theme, or cart items. Use
derivedto keep computed logic centralized and efficient.
More store patterns (readable & custom stores)
You can create readable stores (useful for timers or browser APIs) or fully custom stores with start/stop behavior.
// stores/time.js
import { readable } from 'svelte/store';
export const time = readable(new Date(), function start(set) {
const id = setInterval(() => set(new Date()), 1000);
return function stop() { clearInterval(id); };
});
Custom writable store with methods
// stores/todos.js
import { writable } from 'svelte/store';
function createTodos() {
const { subscribe, set, update } = writable([]);
return {
subscribe,
add: (t) => update(items => [...items, t]),
remove: (id) => update(items => items.filter(i => i.id !== id)),
reset: () => set([])
};
}
export const todos = createTodos();
Use $store in components to auto-subscribe, or store.subscribe() to manually handle subscription and unsubscription when needed.
3) Animations โ fluid UI updates with minimal code โจ
What they are
Svelte’s animate: directive (including the flip helper) animates layout changes โ for example, when list items reorder. Instead of manual measurement and animation code, Svelte can detect position changes and animate smoothly.
Why it matters
Animations improve perceived performance and help users follow changes in the UI. The animate:flip pattern is especially useful for lists that reorder, add, or remove items.
Practical example: animate list reordering with flip
<script>
import { flip } from 'svelte/animate';
let items = ['apple','banana','cherry'];
function shuffle() { items = items.sort(() => Math.random() - 0.5); }
</script>
<button on:click={shuffle}>Shuffle</button>
<ul>
{#each items as item (item)}
<li animate:flip>{{item}}</li>
{/each}
</ul>
Key takeaway
- Use
animate:flipfor intuitive list reorders. For custom animations, theanimatehook and tweening libraries can be used.
Crossfade & custom animations
Svelte also provides helpers like crossfade (for move/fade combined) that let you animate items between lists or states.
<script>
import { crossfade } from 'svelte/transition';
const [send, receive] = crossfade({ duration: 400 });
let inbox = ['msg1', 'msg2'];
let archive = [];
function archiveItem(i) { archive = [...archive, inbox.splice(i,1)[0]]; }
// Use in and out directives with the returned functions
</script>
{#each inbox as m (m)}
<div out:send>{{m}}</div>
{/each}
{#each archive as m (m)}
<div in:receive>{{m}}</div>
{/each}
Key point: use crossfade to animate items moving between containers; configuration supports easing and duration options.
4) Transitions โ enter/leave effects with in:/out: ๐ฌ
What they are
Svelte provides transition directives (in:, out:, and transition:) and built-in effects like fade, fly, slide, and scale. These animate elements entering and leaving the DOM.
Why it matters
Transitions make UI changes feel natural and provide visual feedback for state changes. They are easy to add and are compiled into efficient code.
Practical example: fade + fly transitions
<script>
import { fade, fly } from 'svelte/transition';
let show = true;
</script>
<button on:click={() => show = !show}>Toggle</button>
{#if show}
<div transition:fade={{ duration: 200 }} in:fly={{ x: -20 }} out:fly={{ x: 20 }}>
Hello, I'm animated!
</div>
{/if}
Key takeaway
- Use
in:/out:for entry/exit effects, andtransition:when the same effect is used for both. Keep transitions short and accessible (avoid too long durations).
Custom transitions
You can define a custom transition function for bespoke effects. A transition function returns an object with duration, css, and optionally tick.
<script>
import { cubicOut } from 'svelte/easing';
function blur(node, { duration = 400 }) {
return {
duration,
css: (t) => `filter: blur(${(1 - t) * 5}px); opacity: ${t}`,
easing: cubicOut
};
}
</script>
{#if show}
<div in:blur={{ duration: 300 }}>Custom blur entrance</div>
{/if}
Key point: custom transitions give precise control and can combine transforms, opacity, and CSS filters; prefer hardware-friendly transforms where possible.
Common Pitfalls & Best Practices โ ๏ธโ
Pitfalls
- Overusing transitions/animations can distract users โ prefer subtle effects that reinforce UX.
- Mutating arrays or objects in place can confuse reactivity; prefer creating new arrays (e.g.,
items = items.filter(...)). - Using
$:for heavy side effects is an anti-pattern; use lifecycle hooks or explicit functions for async side effects.
Best Practices
- Keep composability in mind: extract logic to components or helpers when it grows.
- Use stores for shared state and
bind:for quick two-way bindings. - Test animations at realistic frame rates and on low-powered devices.
Svelte-specific best practices:
- Prefer immutable updates for arrays/objects (
items = items.filter(...)) so the change is detected and predictable. - Keep animations short and purposeful (150โ400ms) and provide a way to reduce motion for accessibility.
- Avoid heavy computations in
$:; move expensive work to debounce/memoize or run on-demand. - Use the REPL to prototype animations and transitions quickly โ it makes iterating fast and reduces debugger time.
Deployment & Architecture Notes โ Text Graphs ๐๏ธ
Svelte apps can be delivered as SPAs, pre-rendered static sites, or SSR apps (SvelteKit). Examples:
- Static SSG:
dev -> build -> static files -> CDN -> client - SSR with SvelteKit:
client -> CDN -> edge/server -> SvelteKit renderer -> backend API -> DB
Use CDN and image optimization to improve global load times.
Pros, Cons & Alternatives โ Quick Decision Guide โ๏ธ
Pros:
- Minimal boilerplate and small bundle sizes due to compile-time approach.
- Intuitive reactivity model that feels like writing plain JS.
- Great built-in animations and transitions.
More pros:
- Excellent DX (less ceremony than many frameworks) and fast iteration with HMR.
- Small runtime and efficient updates thanks to compile-time transforms.
Cons:
- Smaller ecosystem than React at enterprise scale (though growing fast).
- Some devs prefer explicit state libraries and patterns that are common in larger React ecosystems.
More cons to consider:
- Less mainstream hiring pool compared to React; may be a factor for large teams.
- Some libraries and ecosystem patterns are less mature than React’s but are rapidly improving.
Alternatives:
- React: larger ecosystem, more third-party options.
- Vue: similar developer ergonomics, different reactive model and larger library ecosystem.
- SvelteKit: pick this when you need SSR/SSG with conventions and adapter options.
Further Reading & Resources ๐
- Official Svelte docs: https://svelte.dev/
- Svelte tutorial: https://svelte.dev/tutorial
- SvelteKit: https://kit.svelte.dev/
- Ryan Carniato & Svelte community blogs: for advanced patterns and performance tips
Additional resources and community:
- Svelte REPL: svelte.dev/repl โ live prototyping tool.
- Svelte Society: sveltesociety.dev โ community resources and links to talks.
- Svelte Mastery & Svelte School โ video courses for hands-on learning.
- MDN: Using the Web Animations API โ helpful for custom animation logic.
Books & advanced reads:
- Blog posts from the Svelte core team and community for performance and advanced patterns.
Conclusion โ Start small, iterate fast ๐
Svelte’s reactive declarations, stores, animations, and transitions provide a compact yet powerful toolkit for building expressive UIs. Start by converting a small interactive component to Svelte and experiment with $: and stores โ youโll often be surprised how concise your code becomes.
Comments