Svelte stands out as a compiler rather than a traditional runtime framework. Unlike React or Vue, which perform significant work in the browser, Svelte shifts this workload to the build step. This results in smaller bundle sizes and potentially better performance.
Why Svelte Feels Different
Svelte's approach is built around several key principles:
- Compilation-First: Svelte compiles components into efficient, imperative JavaScript that directly manipulates the DOM. This eliminates the need for a virtual DOM diffing step.
- Minimal Bundle Size: The compiler includes only the code that is actually used in the final build. Unreferanced components or utilities are omitted, leading to dramatically smaller bundles compared to frameworks that ship a runtime.
- True Reactivity: Reactivity is inherent. You declare a variable and assign to it directly. The framework tracks dependencies and updates the DOM accordingly without need for
setState,ref, orreactivewrappers. This feels natural and less verbose. - Accessibility by Default: The compiler provides warnings for common accessibility issues, such as missing
altattributes on images, encouraging better practices from the start.
Setting Up a Project
Multiple ways exist to start a Svelte project. This guide uses Vite due to its speed.
Create Project with Vite
npm init vite@latest
# Provide your project name when prompted
# Select the `svelte` template (not svelte-ts for this guide)
cd your-project-name
npm install
npm run dev
# Visit http://127.0.0.1:5173 in your browser
After creating the project, clear the global styles in src/app.css to start fresh.
Basic Component Structure
A .svelte file contains the component’s logic, template, and styles. Here is a minimal example that demonstrates core features:
<script>
let name = 'World';
function handleClick() {
name = 'Svelte!';
}
</script>
<div>Hello {name}</div>
<button on:click={handleClick}>Change Name</button>
<style>
div {
color: blue;
}
</style>
- Variables and functions are defined in a
<script>block. - Template expressions use single curly braces
{}. - Event listeners use the
on:eventnamedirective. - Styles are scoped by default.
Core Template Syntax
Interpolation and Expressions
You can interpolate variables, function return values, and expressions directly.
<script>
let count = 5;
let isActive = true;
</script>
<div>{count * 2}</div>
<div>{isActive ? 'Active' : 'Inactive'}</div>
HTML Rendering
To render raw HTML (e.g., from a CMS), use the {@html} directive. Be cautious of XSS risks.
<script>
let rawHtml = '<strong>Important text</strong>';
</script>
<div>{@html rawHtml}</div>
Attribute Binding
Bind any attribute dynamically using {}.
<script>
let imageSrc = 'logo.png';
let altText = 'Company logo';
</script>
<img src={imageSrc} alt={altText} />
Styling
Inline Styles
You can use inline styles with dynamic values.
<script>
let textColor = 'red';
</script>
<p style="color: {textColor};">This text changes color.</p>
Conditional CSS Classes
Apply classes conditionally using the class:classname={condition} syntax.
<script>
let isImportant = true;
</script>
<p class:important={isImportant}>Important message</p>
<style>
.important {
font-weight: bold;
color: orange;
}
</style>
Control Flow
Conditional Rendering ({#if})
Use {#if}, {:else if}, and {:else} blocks.
<script>
let score = 75;
</script>
{#if score >= 90}
<p>Grade: A</p>
{:else if score >= 70}
<p>Grade: B</p>
{:else}
<p>Grade: C or lower</p>
{/if}
Loops ({#each})
Iterate over arrays with {#each}. The syntax is {#each list as item, index}.
<script>
let items = ['Apple', 'Banana', 'Cherry'];
let users = [
{ id: 1, username: 'Alice' },
{ id: 2, username: 'Bob' }
];
</script>
<ul>
{#each items as fruit, i}
<li>{i + 1}: {fruit}</li>
{/each}
</ul>
<ul>
{#each users as { username }}
<li>{username}</li>
{/each}
</ul>
You can also combine with {:else} for empty lists.
Events
Listen to native DOM events using the on:eventname directive.
<script>
function handleInput(event) {
console.log('Input value:', event.target.value);
}
</script>
<input on:input={handleInput} />
Event Modifiers
Chain modifiers after the event type for common behaviors.
<button on:click|once={handler}>Click me only once</button>
<a href="/somewhere" on:click|preventDefault={handler}>Don't follow link</a>
Common modifiers include once, preventDefault, stopPropagation, capture, and passive.
Two-Way Binding with bind:
Svelte provides bind: for two-way data binding, primarily with form elements.
<script>
let name = '';
let isChecked = false;
let selectedOption = 'option2';
</script>
<input type="text" bind:value={name} />
<input type="checkbox" bind:checked={isChecked} />
<select bind:value={selectedOption}>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
<p>Name: {name}, Checked: {isChecked}, Selected: {selectedOption}</p>
For groups like radio buttons or checkboxes, use bind:group={variable}.
Reactivity with $:
The $: label declares reactive statements. They re-run whenever their dependencies change. This is akin to a computed property.
<script>
let items = [1, 2, 3];
let filterText = '';
$: filteredItems = items.filter(item => item > parseInt(filterText) || !filterText);
</script>
<input type="text" bind:value={filterText} />
<ul>
{#each filteredItems as item}
<li>{item}</li>
{/each}
</ul>
Async Data with {#await}
Handle promises directly in the template for loading states.
<script>
async function fetchData() {
// ... your async logic
return { data: 'some result' };
}
let promise = fetchData();
</script>
{#await promise}
<p>Loading...</p>
{:then result}
<p>Result: {result.data}</p>
{:catch error}
<p>Error: {error.message}</p>
{/await}
Component Communication
Passing Props (Parent to Child)
Export a variable from the child component to make it a prop.
Child.svelte
<script>
export let message = 'Default message';
</script>
<p>{message}</p>
Parent.svelte
<script>
import Child from './Child.svelte';
</script>
<Child message="Hello from parent" />
Dispatching Events (Child to Parent)
Use createEventDispatcher to send events upward.
Child.svelte
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sendValue() {
dispatch('valuechange', { newValue: 42 });
}
</script>
<button on:click={sendValue}>Send Value</button>
Parent.svelte
<script>
import Child from './Child.svelte';
function handleValueChange(event) {
console.log('Received from child:', event.detail.newValue);
}
</script>
<Child on:valuechange={handleValueChange} />
Slots
Create flexible components with the <slot> element.
Card.svelte
<div class="card">
<slot></slot>
</div>
<style>
.card {
border: 1px solid #ccc;
padding: 1rem;
border-radius: 8px;
}
</style>
Parent.svelte
<script>
import Card from './Card.svelte';
</script>
<Card>
<h2>Custom Content</h2>
<p>This is placed in the slot.</p>
</Card>
Lifecycle Functions
Import lifecycle functions from the svelte package.
<script>
import { onMount, onDestroy, beforeUpdate, afterUpdate, tick } from 'svelte';
let count = 0;
onMount(() => {
console.log('Component mounted');
});
onDestroy(() => {
console.log('Component about to be destroyed');
});
beforeUpdate(() => {
console.log('About to update');
});
afterUpdate(() => {
console.log('Updated');
});
async function increment() {
count++;
// Ensure the DOM has updated after this change
await tick();
console.log('DOM now reflects the new count');
}
</script>
<p>Count: {count}</p>
<button on:click={increment}>+1</button>
Summary
Svelte offers a refreshing developer experience by moving complexity to compile time. Its core features—minimal runtime, true reactivity, scoped styles, and simple component APIs—make it a strong candidate for many web projects. While its ecosystem is smaller than React or Vue's, its focus on performance and developer ergonomics continues to attract a growing community.