Configuration
Language Name:svelteFile Extensions:
.svelte, .app.svelteEditor: Script editor
Compiler: Svelte 5 compiler
Runtime: Svelte 5.x with automatic imports
Features
Basic Component
<script>
let count = $state(0);
function increment() {
count++;
}
function decrement() {
count--;
}
</script>
<div class="counter">
<h2>Count: {count}</h2>
<button on:click={increment}>Increment</button>
<button on:click={decrement}>Decrement</button>
</div>
<style>
.counter {
padding: 2rem;
text-align: center;
}
button {
margin: 0.5rem;
padding: 0.5rem 1rem;
background: #ff3e00;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #ff5722;
}
</style>
Runes (Svelte 5)
Svelte 5 introduces runes for reactive state:<script>
// Reactive state
let count = $state(0);
// Derived state
let doubled = $derived(count * 2);
// Side effects
$effect(() => {
console.log(`Count is now ${count}`);
});
// Props
let { title, initialCount = 0 } = $props();
</script>
<div>
<h1>{title}</h1>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
</div>
Reactive Declarations (Svelte 4 syntax)
Traditional reactive syntax still supported:<script>
let count = 0;
// Reactive declaration
$: doubled = count * 2;
// Reactive statement
$: {
console.log(`Count: ${count}`);
console.log(`Doubled: ${doubled}`);
}
// Reactive if
$: if (count > 10) {
console.log('Count is greater than 10');
}
</script>
Component Props
Define and use props:<script>
// Svelte 5 runes
let { title, count = 0, onClick } = $props();
// Or Svelte 4 style
export let title;
export let count = 0;
export let onClick = () => {};
</script>
<div>
<h2>{title}</h2>
<p>Count: {count}</p>
<button on:click={onClick}>Click</button>
</div>
Events
Handle events and dispatch custom events:<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function handleClick() {
dispatch('submit', {
timestamp: Date.now(),
});
}
function handleInput(event) {
console.log(event.target.value);
}
</script>
<div>
<input on:input={handleInput} />
<button on:click={handleClick}>Submit</button>
</div>
Bindings
Two-way data binding:<script>
let name = $state('');
let checked = $state(false);
let selected = $state('');
let value = $state(50);
</script>
<div>
<!-- Text input -->
<input bind:value={name} />
<p>Hello, {name}!</p>
<!-- Checkbox -->
<input type="checkbox" bind:checked />
<p>Checked: {checked}</p>
<!-- Select -->
<select bind:value={selected}>
<option value="a">Option A</option>
<option value="b">Option B</option>
</select>
<!-- Range -->
<input type="range" bind:value min="0" max="100" />
<p>Value: {value}</p>
</div>
Conditionals
Conditional rendering:<script>
let user = $state({ loggedIn: false });
let count = $state(0);
</script>
{#if user.loggedIn}
<p>Welcome back!</p>
{:else if user.pending}
<p>Loading...</p>
{:else}
<p>Please log in</p>
{/if}
{#if count > 10}
<p>Count is greater than 10</p>
{/if}
Loops
List rendering with{#each}:
<script>
let items = $state([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' },
]);
</script>
<ul>
{#each items as item (item.id)}
<li>{item.name}</li>
{:else}
<li>No items</li>
{/each}
</ul>
<!-- With index -->
{#each items as item, index (item.id)}
<p>{index + 1}. {item.name}</p>
{/each}
Await Blocks
Handle async data:<script>
async function fetchUsers() {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
return response.json();
}
let usersPromise = $state(fetchUsers());
</script>
{#await usersPromise}
<p>Loading...</p>
{:then users}
<ul>
{#each users as user}
<li>{user.name}</li>
{/each}
</ul>
{:catch error}
<p>Error: {error.message}</p>
{/await}
Component Features
Slots
Component composition:<!-- Card.svelte -->
<div class="card">
<div class="card-header">
<slot name="header">Default Header</slot>
</div>
<div class="card-body">
<slot>Default content</slot>
</div>
<div class="card-footer">
<slot name="footer" />
</div>
</div>
<!-- Usage -->
<script>
import Card from './Card.svelte';
</script>
<Card>
<h2 slot="header">Custom Header</h2>
<p>Card content goes here</p>
<button slot="footer">Action</button>
</Card>
Context API
Share data between components:<!-- Parent.svelte -->
<script>
import { setContext } from 'svelte';
setContext('theme', {
color: 'dark',
toggle: () => {
// toggle logic
},
});
</script>
<!-- Child.svelte -->
<script>
import { getContext } from 'svelte';
const theme = getContext('theme');
</script>
<button on:click={theme.toggle}>
Current theme: {theme.color}
</button>
Lifecycle
Component lifecycle hooks:<script>
import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';
onMount(() => {
console.log('Component mounted');
return () => {
console.log('Cleanup on unmount');
};
});
onDestroy(() => {
console.log('Component destroyed');
});
beforeUpdate(() => {
console.log('Before update');
});
afterUpdate(() => {
console.log('After update');
});
</script>
Stores
Reactive stores for shared state:<script>
import { writable, derived, readable } from 'svelte/store';
// Writable store
const count = writable(0);
// Derived store
const doubled = derived(count, $count => $count * 2);
// Readable store
const time = readable(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return () => clearInterval(interval);
});
// Auto-subscription with $
$: console.log('Count:', $count);
</script>
<div>
<p>Count: {$count}</p>
<p>Doubled: {$doubled}</p>
<p>Time: {$time.toLocaleTimeString()}</p>
<button on:click={() => count.update(n => n + 1)}>
Increment
</button>
</div>
Animations & Transitions
Transitions
<script>
import { fade, fly, slide, scale } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
let visible = $state(true);
</script>
{#if visible}
<div transition:fade>Fades in and out</div>
{/if}
{#if visible}
<div transition:fly={{ y: 200, duration: 500 }}>
Flies in and out
</div>
{/if}
{#if visible}
<div transition:scale={{ start: 0, duration: 500, easing: quintOut }}>
Scales in and out
</div>
{/if}
<button on:click={() => visible = !visible}>
Toggle
</button>
Animations
<script>
import { flip } from 'svelte/animate';
import { quintOut } from 'svelte/easing';
let items = $state([1, 2, 3, 4, 5]);
function shuffle() {
items = items.sort(() => Math.random() - 0.5);
}
</script>
<button on:click={shuffle}>Shuffle</button>
{#each items as item (item)}
<div animate:flip={{ duration: 500, easing: quintOut }}>
{item}
</div>
{/each}
Styling
Scoped Styles
Styles are scoped by default:<div class="card">
<h2>Title</h2>
</div>
<style>
/* Only applies to this component */
.card {
border: 1px solid #ccc;
padding: 1rem;
}
h2 {
color: #333;
}
</style>
Global Styles
<style>
:global(body) {
margin: 0;
font-family: sans-serif;
}
/* Scoped to component, global class name */
:global(.button) {
padding: 0.5rem 1rem;
}
</style>
Dynamic Styles
<script>
let color = $state('blue');
let size = $state(16);
</script>
<p style="color: {color}; font-size: {size}px">
Styled text
</p>
<p style:color style:font-size="{size}px">
Alternative syntax
</p>
Example Projects
Todo App
<script>
let todos = $state([]);
let newTodo = $state('');
function addTodo() {
if (newTodo.trim()) {
todos = [...todos, {
id: Date.now(),
text: newTodo,
done: false,
}];
newTodo = '';
}
}
function toggleTodo(id) {
todos = todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
);
}
function deleteTodo(id) {
todos = todos.filter(todo => todo.id !== id);
}
</script>
<div class="todo-app">
<h1>Todo List</h1>
<input
bind:value={newTodo}
on:keypress={e => e.key === 'Enter' && addTodo()}
placeholder="Add a todo..."
/>
<button on:click={addTodo}>Add</button>
<ul>
{#each todos as todo (todo.id)}
<li class:done={todo.done}>
<input
type="checkbox"
checked={todo.done}
on:change={() => toggleTodo(todo.id)}
/>
{todo.text}
<button on:click={() => deleteTodo(todo.id)}>Delete</button>
</li>
{/each}
</ul>
</div>
<style>
.done {
text-decoration: line-through;
opacity: 0.6;
}
</style>
Code Formatting
Automatic formatting with Prettier:<!-- Before formatting -->
<script>let count=0;</script>
<button on:click={()=>count++}>Click</button>
<!-- After formatting (Ctrl+Shift+F) -->
<script>
let count = 0;
</script>
<button on:click={() => count++}>Click</button>
Best Practices
Use Runes (Svelte 5)
Use Runes (Svelte 5)
Prefer runes for clearer reactive state:
<script>
// Good (Svelte 5)
let count = $state(0);
let doubled = $derived(count * 2);
// Old (Svelte 4)
let count = 0;
$: doubled = count * 2;
</script>
Key Blocks in Each
Key Blocks in Each
Always provide keys for list items:
<!-- Good -->
{#each items as item (item.id)}
<div>{item.name}</div>
{/each}
<!-- Bad - no key -->
{#each items as item}
<div>{item.name}</div>
{/each}
Reactive Declarations
Reactive Declarations
Keep reactive declarations simple:
<script>
// Good
$: doubled = count * 2;
// Avoid complex logic
$: {
// Multiple lines of complex calculations
// Better to use a function
}
</script>
Related
Vue
Alternative component framework
React
JSX-based framework
SCSS
Use SCSS in style blocks
TypeScript
Add type safety to Svelte