What is Svelte?
Svelte is a component framework with a twist: it is a compiler. Instead of shipping a runtime that interprets your components in the browser, Svelte analyses them at build time and emits plain JavaScript that updates the DOM directly. There is no virtual DOM and almost no framework code in the final bundle.
That design changes the developer experience. Reactivity is part of the language, styles are scoped by default, and transitions are built in, so components end up short. The trade-off is that you need a build step, but modern tooling makes that a given.
Single-file components
A Svelte component is a .svelte file with a script block, markup and optional scoped styles.
<!-- Card.svelte -->
<script>
let { title, children } = $props();
</script>
<article class="card">
<h3>{title}</h3>
{@render children?.()}
</article>
<style>
.card {
border-radius: 1rem;
padding: 1.5rem;
}
</style>
Styles in a component are scoped to it by default, so the .card rule cannot leak. $props() reads the props passed by the parent, and snippets (children) let a parent pass markup into a child.
Reactivity with runes
Svelte 5 introduced runes, explicit primitives that make reactivity predictable. $state declares reactive state, $derived computes values, and $effect runs side effects.
<!-- Search.svelte -->
<script>
let query = $state("");
let results = $state([]);
let hasResults = $derived(results.length > 0);
$effect(() => {
if (!query) return;
const controller = new AbortController();
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then((r) => r.json())
.then((data) => (results = data))
.catch(() => {});
return () => controller.abort();
});
</script>
<input bind:value={query} placeholder="Search" />
{#if hasResults}
<ul>
{#each results as item (item.id)}
<li>{item.name}</li>
{/each}
</ul>
{/if}
Runes work in .svelte.js and .svelte.ts files too, so you can extract reactive logic into reusable modules the same way React uses custom hooks.
Props, events and snippets
Props flow down and are declared with $props(). Instead of a special event system, Svelte 5 uses callback props for child-to-parent communication.
<!-- Child.svelte -->
<script>
let { label, onselect } = $props();
</script>
<button onclick={() => onselect(label)}>{label}</button>
<!-- Parent.svelte -->
<Child label="Save" onselect={(value) => save(value)} />
Snippets replace slots for reusable markup regions, and {@render} inserts them where you want.
Control flow
Svelte has dedicated template blocks for conditionals and loops, and the #each block supports a keyed expression for efficient updates.
<!-- list.svelte -->
{#if users.length > 0}
<ul>
{#each users as user (user.id)}
<li>{user.name}</li>
{/each}
</ul>
{:else}
<p>No users yet.</p>
{/if}
Because the compiler knows the structure, these blocks generate targeted DOM operations rather than a generic reconciliation pass.
Stores and shared state
For state shared across many components, Svelte provides stores: a writable store holds a value and notifies subscribers, and readable and derived cover other cases.
// counter.js
import { writable, derived } from "svelte/store";
export const count = writable(0);
export const double = derived(count, ($count) => $count * 2);
<!-- Display.svelte -->
<script>
import { count, double } from "./counter.js";
</script>
<p>{$count} doubled is {$double}</p>
The $ prefix auto-subscribes and unsubscribes for you. With runes, a .svelte.js module exporting $state can achieve the same thing without the store API, which many new projects prefer for application-wide values.
Transitions and animations
Motion is built in. The transition, animate and in/out directives add movement without an external library.
<!-- toast.svelte -->
<script>
import { fade, fly } from "svelte/transition";
let visible = $state(false);
</script>
<button onclick={() => (visible = !visible)}>Toggle</button>
{#if visible}
<div transition:fly={{ y: 20, duration: 200 }}>
Saved!
</div>
{/if}
Because transitions are compiled, they are efficient and tree-shakeable, and they respect the user’s reduced-motion preference when you use the built-in helpers.
SvelteKit
SvelteKit is the official application framework. It adds file-based routing, server rendering, data loading and form actions on top of Svelte.
src/routes/
├── +page.svelte # a page
├── +layout.svelte # shared layout
├── about/+page.svelte # /about
└── blog/[slug]/+page.js # load data for /blog/:slug
If you are building more than a widget, SvelteKit is the recommended starting point. It handles the conventions that every real app needs so you can focus on features.
Best practices
- Use runes (
$state,$derived,$effect) in new code. - Prefer
$derivedfor computed values and reserve$effectfor side effects. - Keep components small and extract reusable logic into
.svelte.jsmodules. - Use keyed
{#each}blocks when items can reorder. - Scope styles by default and avoid
:globalunless necessary. - Share cross-component state with stores or a reactive module.
- Start new apps with SvelteKit rather than wiring a build yourself.
Common mistakes
- Using
$effectto compute a value that$derivedshould handle. - Forgetting the key in
{#each}and breaking list updates. - Mutating a
$stateobject’s nested property in a way that bypasses reactivity in older patterns. - Reaching for a global store when local state would do.
- Assuming Svelte has no runtime at all — there is a small one.
- Ignoring accessibility because the compiler makes markup easy to write quickly.
Where to go next
Svelte is a fast, elegant way to build interfaces, and SvelteKit turns it into a full application framework. Compare the compiler approach with React and Vue, and keep your JavaScript sharp. Then build a small app with a store and a transition to feel the difference.