Skip to content

Introduction — Why Svelte for React Developers

You already know React. You understand components, props, state, and the render cycle. Svelte builds on those same ideas but solves them differently — as a compiler rather than a runtime library. This course shows you exactly what changes, what stays familiar, and how to carry your React intuition into Svelte 5.

React ships a JavaScript runtime (~45 kB gzip for React + ReactDOM) that runs in the browser and manages a virtual DOM tree. Every setState call triggers a reconciliation pass: React diffs the new vDOM against the previous one and patches the real DOM.

Svelte works at build time. Your .svelte files are compiled into tight, framework-free JavaScript that talks directly to the DOM. There is no virtual DOM to diff, no reconciler to ship. The output is plain document.createElement and targeted element.textContent = assignments — the absolute minimum the browser needs.

Practical consequences:

  • Smaller bundles. A Svelte app ships almost no framework overhead. A “Hello World” Svelte component compiles to a few hundred bytes.
  • Faster updates. Instead of diffing a vDOM tree, compiled Svelte code directly updates only the DOM nodes that actually changed.
  • No React import needed. Your components are standalone compiled modules.

React requires explicit state declarations (useState) and a separate setter function for every piece of reactive state. In Svelte 5 you declare reactive state with $state() and mutate it with plain assignment — the compiler instruments the mutation for you.

React
function Greeting() {
const [name, setName] = React.useState('World');
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<p>Hello, {name}!</p>
</div>
);
}
Svelte
<script>
let name = $state('World');
</script>
<input bind:value={name} />
<p>Hello, {name}!</p>

Notice what disappears in the Svelte version:

  • No useState import, no setter function (setName).
  • name is declared once with $state() — assigning to it is enough to trigger a re-render.
  • bind:value handles the two-way sync between the input and the variable (no onChange wiring).

React has no opinion on CSS. Teams reach for CSS Modules, styled-components, Tailwind, or some combination. Svelte ships scoped styles as a first-class language feature: a <style> block inside a .svelte file is automatically scoped to that component. No extra tooling, no class name mangling configuration, no runtime overhead.

<style>
/* This rule only affects <p> tags inside THIS component */
p { color: steelblue; font-size: 1.2rem; }
</style>

At compile time Svelte generates a unique attribute (e.g. svelte-abc123) and rewrites the selector to p[svelte-abc123]. Global styles never leak in; your styles never leak out.

ModuleTopics
IntroCompiler model, reactivity basics, scoped styles
ComponentsProps, events, slots/snippets, lifecycle
Reactivity$state, $derived, $effect, stores
Bindingsbind:value, bind:checked, bind:this
Control Flow{#if}, {#each}, {#await}, {#key}
StylingScoped styles, global styles, CSS variables, transitions
RoutingSvelteKit pages, layouts, load functions
AdvancedContext API, actions, custom stores, TypeScript
React
function App() {
const [name, setName] = React.useState('React developer');
const [count, setCount] = React.useState(0);
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<p>Hello, {name}!</p>
<button onClick={() => setCount(c => c + 1)}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
</div>
);
}
Svelte
<script>
let name = $state('Svelte developer');
let count = $state(0);
</script>
<input bind:value={name} />
<p>Hello, {name}!</p>
<button onclick={() => count++}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

The playground below is a complete, editable Svelte 5 component. Change the name, click the button, tweak the styles — everything runs in your browser.

What makes Svelte fundamentally different from React?
How do you create reactive state in Svelte 5?
How are component styles scoped in Svelte?