Svelte Cheatsheet¶
Svelte - Cybernetically Enhanced Web Apps
Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the browser, Svelte shifts that work into a compile step that happens when you build your app.
Table of Contents¶
- Installation
- Project Creation
- Project Structure
- Component Syntax
- Reactivity
- Props
- Logic and Templating
- Events
- Bindings
- Lifecycle Functions
- Stores
- Transitions and Animations
- SvelteKit
- Deployment
- Testing
- Best Practices
Installation¶
Prerequisites¶
# Node.js version requirements
node --version # Should be 16 or later
# Check npm version
npm --version
# Update npm if needed
npm install -g npm@latest
Create Svelte Project¶
# Create new SvelteKit project (recommended)
npm create svelte@latest my-app
# Create a basic Svelte project (without SvelteKit)
npm create vite@latest my-app -- --template svelte
# Navigate to project
cd my-app
# Install dependencies
npm install
# Start development server
npm run dev
Manual Installation (Vite)¶
# Create project directory
mkdir my-svelte-project
cd my-svelte-project
# Initialize package.json
npm init -y
# Install Vite and Svelte plugin
npm install --save-dev vite @sveltejs/vite-plugin-svelte
# Install Svelte
npm install svelte
# Create vite.config.js
// vite.config.js
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte()]
});
Package.json Scripts¶
{
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
}
}
Project Creation¶
SvelteKit Project Setup¶
# Interactive setup
npm create svelte@latest my-app
# Select options:
# - Skeleton project (basic setup)
# - Demo app (with examples)
# - Library project
# Add TypeScript
# Add ESLint
# Add Prettier
# Add Playwright for browser testing
# Add Vitest for unit testing
Development Commands¶
# Start development server
npm run dev
# Start with specific port
npm run dev -- --port 3000
# Start with specific host
npm run dev -- --host 0.0.0.0
# Start with open browser
npm run dev -- --open
Build Commands¶
# Build for production
npm run build
# Preview production build
npm run preview
# Check for issues
npm run check
Project Structure¶
SvelteKit Project Structure¶
my-sveltekit-app/
├── src/
│ ├── lib/ # Library code (components, utils)
│ │ ├── components/ # Reusable components
│ │ └── utils/ # Utility functions
│ ├── routes/ # File-based routing
│ │ ├── +page.svelte # Homepage
│ │ ├── about/ # /about route
│ │ │ └── +page.svelte
│ │ └── blog/
│ │ ├── +page.svelte # /blog index
│ │ └── [slug]/
│ │ └── +page.svelte
│ ├── app.html # HTML template
│ └── app.d.ts # TypeScript declarations
├── static/ # Static assets
├── tests/ # Test files
├── package.json
├── svelte.config.js # SvelteKit configuration
└── tsconfig.json
Vite Project Structure¶
my-vite-svelte-app/
├── public/ # Static assets
├── src/
│ ├── assets/ # Assets
│ ├── lib/ # Svelte components
│ └── main.js # App entry point
├── index.html # HTML template
├── package.json
└── vite.config.js
Component Syntax¶
Basic Component¶
<!-- src/lib/components/Greeting.svelte -->
<script>
// Component logic
let name = 'World';
</script>
<main>
<h1>Hello, {name}!</h1>
<p>This is a Svelte component.</p>
</main>
<style>
/* Scoped styles */
main {
text-align: center;
padding: 2rem;
}
h1 {
color: #ff3e00;
font-size: 2.5rem;
}
p {
color: #333;
}
</style>
Component with Props¶
<!-- src/lib/components/Welcome.svelte -->
<script>
export let name;
export let message = 'Welcome to our app!';
</script>
<div>
<h2>Hello, {name}!</h2>
<p>{message}</p>
</div>
<!-- Usage -->
<!-- <Welcome name="John" /> -->
<!-- <Welcome name="Jane" message="Glad to have you here!" /> -->
Component with Logic¶
<!-- src/lib/components/Counter.svelte -->
<script>
let count = 0;
function increment() {
count += 1;
}
function decrement() {
count -= 1;
}
function reset() {
count = 0;
}
</script>
<div class="counter">
<h3>Counter</h3>
<p>Count: {count}</p>
<div class="buttons">
<button on:click={decrement}>-</button>
<button on:click={increment}>+</button>
<button on:click={reset}>Reset</button>
</div>
</div>
<style>
.counter {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
text-align: center;
}
.buttons {
display: flex;
gap: 0.5rem;
justify-content: center;
margin-top: 1rem;
}
</style>
Reactivity¶
Reactive Assignments¶
<script>
let count = 0;
function handleClick() {
// Svelte automatically detects this assignment is reactive
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
Reactive Declarations¶
<script>
let count = 0;
// This will be re-evaluated whenever `count` changes
$: doubled = count * 2;
$: quadrupled = doubled * 2;
function handleClick() {
count += 1;
}
</script>
<p>{count} * 2 = {doubled}</p>
<p>{doubled} * 2 = {quadrupled}</p>
<button on:click={handleClick}>Increment</button>
Reactive Statements¶
<script>
let count = 0;
// This block of code runs whenever `count` changes
$: {
console.log(`The count is ${count}`);
if (count >= 10) {
alert('Count is 10 or more!');
}
}
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>Count: {count}</button>
Updating Arrays and Objects¶
<script>
let numbers = [1, 2, 3];
let user = { name: 'John', age: 30 };
function addNumber() {
// To trigger reactivity, you must assign to the variable
numbers = [...numbers, numbers.length + 1];
}
function changeName() {
user.name = 'Jane';
// This won't trigger reactivity on its own
// To make it reactive, reassign the object
user = user;
}
</script>
<p>Numbers: {numbers.join(', ')}</p>
<button on:click={addNumber}>Add Number</button>
<p>User: {user.name}, {user.age}</p>
<button on:click={changeName}>Change Name</button>
Props¶
Declaring Props¶
<script>
// Declare props with `export let`
export let name;
export let age = 25; // Default value
export let tags = [];
export let user = { id: 1, name: 'Guest' };
// Renaming props
export let title as heading;
</script>
<h2>{heading}</h2>
<p>Welcome, {name} ({age})</p>
{#if tags.length > 0}
<p>Tags: {tags.join(', ')}</p>
{/if}
Passing Props¶
<!-- ParentComponent.svelte -->
<script>
import UserProfile from './UserProfile.svelte';
let user = {
name: 'John Doe',
age: 30,
tags: ['developer', 'svelte']
};
</script>
<UserProfile
name={user.name}
age={user.age}
tags={user.tags}
title="User Profile"
/>
<!-- Shorthand for passing props -->
<UserProfile {...user} title="User Profile" />
Type Checking with TypeScript¶
<script lang="ts">
export let name: string;
export let age: number;
export let active: boolean = false;
export let onSave: (data: any) => void;
</script>
<p>{name} is {age} years old.</p>
<button on:click={() => onSave({ name, age })}>
Save
</button>
Logic and Templating¶
Conditional Rendering¶
<script>
let loggedIn = false;
let user = { name: 'John' };
</script>
{#if loggedIn}
<p>Welcome back, {user.name}!</p>
{:else}
<p>Please log in.</p>
{/if}
{#if user.role === 'admin'}
<p>Admin controls</p>
{:else if user.role === 'editor'}
<p>Editor controls</p>
{:else}
<p>User controls</p>
{/if}
Looping¶
<script>
let cats = [
{ id: '1', name: 'Whiskers' },
{ id: '2', name: 'Mittens' },
{ id: '3', name: 'Shadow' }
];
</script>
<ul>
{#each cats as cat (cat.id)}
<li>{cat.name}</li>
{/each}
</ul>
<!-- With index -->
<ul>
{#each cats as cat, i}
<li>{i + 1}: {cat.name}</li>
{/each}
</ul>
<!-- With else block -->
{#each cats as cat}
<p>{cat.name}</p>
{:else}
<p>No cats found.</p>
{/each}
Await Blocks¶
<script>
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
if (!res.ok) throw new Error('Failed to fetch posts');
return await res.json();
}
let promise = getPosts();
</script>
{#await promise}
<p>Loading posts...</p>
{:then posts}
<ul>
{#each posts as post}
<li>{post.title}</li>
{/each}
</ul>
{:catch error}
<p>Error: {error.message}</p>
{/await}
HTML Rendering¶
<script>
let htmlContent = '<h1>This is raw HTML</h1><p>Use with caution.</p>';
</script>
<!-- Render raw HTML -->
{@html htmlContent}
<!-- Debugging -->
{@debug count, user}
Events¶
DOM Events¶
<script>
function handleClick() {
alert('Button clicked!');
}
function handleInput(event) {
console.log('Input value:', event.target.value);
}
</script>
<button on:click={handleClick}>Click me</button>
<input on:input={handleInput} placeholder="Type something" />
<!-- Inline handler -->
<button on:click={() => console.log('Clicked!')}>Inline</button>
Event Modifiers¶
<!-- Prevent default action -->
<form on:submit|preventDefault={handleSubmit}></form>
<!-- Stop event propagation -->
<div on:click={handleOuter}>
<button on:click|stopPropagation={handleInner}>Inner</button>
</div>
<!-- Run handler only once -->
<button on:click|once={doSomething}>Once</button>
<!-- Run handler if event is not passive -->
<div on:wheel|passive={handleWheel}></div>
<!-- Run handler during capture phase -->
<div on:click|capture={handleCapture}></div>
<!-- Self modifier -->
<div on:click|self={handleSelf}></div>
Component Events¶
<!-- InnerComponent.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function notify() {
dispatch('message', {
text: 'Hello from the inner component!'
});
}
</script>
<button on:click={notify}>Notify Parent</button>
<!-- ParentComponent.svelte -->
<script>
import InnerComponent from './InnerComponent.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<InnerComponent on:message={handleMessage} />
Event Forwarding¶
<!-- CustomButton.svelte -->
<button on:click>
<slot />
</button>
<!-- ParentComponent.svelte -->
<script>
import CustomButton from './CustomButton.svelte';
function handleClick() {
console.log('Custom button clicked!');
}
</script>
<CustomButton on:click={handleClick}>Click me</CustomButton>
Bindings¶
Value Bindings¶
<script>
let name = 'John';
let message = 'Hello';
let selected = 'option2';
let checked = true;
let value = 5;
</script>
<!-- Text input -->
<input bind:value={name} />
<!-- Textarea -->
<textarea bind:value={message}></textarea>
<!-- Select -->
<select bind:value={selected}>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
<!-- Checkbox -->
<input type="checkbox" bind:checked={checked} />
<!-- Range slider -->
<input type="range" bind:value min="0" max="10" />
Group Bindings¶
<script>
let flavors = ['Vanilla', 'Chocolate', 'Strawberry'];
let selectedFlavors = ['Vanilla'];
let scoop = 'one';
</script>
<!-- Checkbox group -->
{#each flavors as flavor}
<label>
<input type="checkbox" bind:group={selectedFlavors} value={flavor} />
{flavor}
</label>
{/each}
<!-- Radio group -->
<label>
<input type="radio" bind:group={scoop} value="one" /> One scoop
</label>
<label>
<input type="radio" bind:group={scoop} value="two" /> Two scoops
</label>
Component Bindings¶
<!-- InnerComponent.svelte -->
<script>
export let value;
</script>
<input bind:value />
<!-- ParentComponent.svelte -->
<script>
import InnerComponent from './InnerComponent.svelte';
let text = 'Initial value';
</script>
<InnerComponent bind:value={text} />
<p>Value: {text}</p>
Other Bindings¶
<!-- bind:this -->
<script>
let myCanvas;
onMount(() => {
const ctx = myCanvas.getContext('2d');
// Draw on canvas
});
</script>
<canvas bind:this={myCanvas}></canvas>
<!-- bind:clientWidth, bind:clientHeight -->
<script>
let width, height;
</script>
<div bind:clientWidth={width} bind:clientHeight={height}>
Dimensions: {width} x {height}
</div>
Lifecycle Functions¶
onMount¶
<script>
import { onMount } from 'svelte';
onMount(() => {
// Runs after the component is first rendered to the DOM
console.log('Component mounted');
const interval = setInterval(() => {
console.log('tick');
}, 1000);
// Cleanup function
return () => {
clearInterval(interval);
console.log('Component unmounted');
};
});
</script>
beforeUpdate and afterUpdate¶
<script>
import { beforeUpdate, afterUpdate } from 'svelte';
let count = 0;
beforeUpdate(() => {
// Runs before the DOM is updated
console.log('Before update');
});
afterUpdate(() => {
// Runs after the DOM is updated
console.log('After update');
});
</script>
<button on:click={() => count++}>Count: {count}</button>
onDestroy¶
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
// Runs when the component is about to be destroyed
console.log('Component destroyed');
});
</script>
tick¶
<script>
import { tick } from 'svelte';
let text = 'Initial';
async function updateText() {
text = 'Updated';
// Wait for the DOM to update
await tick();
console.log('DOM updated');
}
</script>
<p>{text}</p>
<button on:click={updateText}>Update</button>
Stores¶
Writable Stores¶
// stores.js
import { writable } from 'svelte/store';
export const count = writable(0);
export const user = writable({ name: 'Guest', loggedIn: false });
<!-- Component.svelte -->
<script>
import { count, user } from './stores.js';
function increment() {
count.update(n => n + 1);
}
function login() {
user.set({ name: 'John', loggedIn: true });
}
// Auto-subscription with $ prefix
$: console.log(`Count is ${$count}`);
</script>
<h1>Count: {$count}</h1>
<button on:click={increment}>Increment</button>
{#if $user.loggedIn}
<p>Welcome, {$user.name}!</p>
{:else}
<button on:click={login}>Login</button>
{/if}
Readable Stores¶
// stores.js
import { readable } from 'svelte/store';
export const time = readable(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return () => clearInterval(interval);
});
<!-- Clock.svelte -->
<script>
import { time } from './stores.js';
$: formattedTime = $time.toLocaleTimeString();
</script>
<p>Current time: {formattedTime}</p>
Derived Stores¶
// stores.js
import { derived } from 'svelte/store';
import { time } from './stores.js';
export const elapsed = derived(
time,
$time => Math.round(($time - new Date()) / 1000)
);
<!-- Elapsed.svelte -->
<script>
import { elapsed } from './stores.js';
</script>
<p>Elapsed time: {$elapsed}s</p>
Custom Stores¶
// stores.js
import { writable } from 'svelte/store';
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const count = createCount();
Transitions and Animations¶
Transitions¶
<script>
import { fade, fly, slide, scale } from 'svelte/transition';
let visible = true;
</script>
<label>
<input type="checkbox" bind:checked={visible} />
Toggle visibility
</label>
{#if visible}
<div transition:fade>Fades in and out</div>
<div transition:fly={{ y: 200, duration: 2000 }}>Flies in and out</div>
<div transition:slide>Slides in and out</div>
<div transition:scale>Scales in and out</div>
{/if}
Animations¶
<script>
import { flip } from 'svelte/animate';
let list = [1, 2, 3];
function shuffle() {
list = list.sort(() => Math.random() - 0.5);
}
</script>
<button on:click={shuffle}>Shuffle</button>
{#each list as item (item)}
<div animate:flip>{item}</div>
{/each}
Motion¶
<script>
import { spring } from 'svelte/motion';
let coords = spring({ x: 50, y: 50 }, {
stiffness: 0.1,
damping: 0.25
});
function handleMousemove(event) {
coords.set({ x: event.clientX, y: event.clientY });
}
</script>
<div on:mousemove={handleMousemove} class="container">
<div class="dot" style="transform: translate({$coords.x}px, {$coords.y}px)"></div>
</div>
SvelteKit¶
Routing¶
# File-based routing
src/routes/
├── +page.svelte # Homepage
├── about/+page.svelte # /about
├── blog/[slug]/+page.svelte # /blog/[slug]
└── +layout.svelte # Root layout
Data Loading¶
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
export let data;
const { post } = data;
</script>
<h1>{post.title}</h1>
<div>{@html post.content}</div>
<!-- src/routes/blog/[slug]/+page.js -->
export async function load({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return { post };
}
API Routes¶
// src/routes/api/posts/+server.js
export async function GET() {
const posts = await getPostsFromDB();
return new Response(JSON.stringify(posts));
}
export async function POST({ request }) {
const data = await request.json();
const newPost = await createPost(data);
return new Response(JSON.stringify(newPost), { status: 201 });
}
Layouts¶
<!-- src/routes/+layout.svelte -->
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
</nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© 2024 My App</p>
</footer>
Deployment¶
Static Site (Adapter Static)¶
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html'
})
}
};
Vercel¶
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
export default {
kit: {
adapter: adapter()
}
};
Netlify¶
// svelte.config.js
import adapter from '@sveltejs/adapter-netlify';
export default {
kit: {
adapter: adapter()
}
};
Node.js Server¶
// svelte.config.js
import adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: adapter()
}
};
Testing¶
Vitest¶
// tests/Counter.test.js
import { render, fireEvent } from '@testing-library/svelte';
import Counter from '../src/lib/components/Counter.svelte';
it('increments counter', async () => {
const { getByText } = render(Counter);
const button = getByText('Increment');
await fireEvent.click(button);
expect(getByText('Count: 1')).toBeInTheDocument();
});
Playwright¶
// tests/e2e/home.spec.js
import { test, expect } from '@playwright/test';
test('homepage has correct title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My App/);
});
Best Practices¶
- Component Organization: Keep components small and focused. Use a
lib/components
directory for reusable components. - Reactivity: Use reactive statements (
$:
) for computed values. Avoid complex logic in templates. - Stores: Use stores for global state management. Create custom stores for complex logic.
- Props: Use props for passing data down to child components. Use
export let
to declare props. - Events: Use component events for communication from child to parent. Use
createEventDispatcher
. - Lifecycle: Use
onMount
for fetching data and interacting with the DOM. Use cleanup functions to prevent memory leaks. - SvelteKit: Use SvelteKit for new projects to get routing, data loading, and server-side rendering out of the box.
Summary¶
Svelte is a powerful and efficient framework for building web applications. Its compile-time approach results in smaller bundles and faster performance. Key features include:
- Reactivity: Simple and powerful reactivity system
- Component-based: Build applications with reusable components
- Scoped Styles: CSS is scoped to components by default
- Stores: Built-in state management solution
- Transitions and Animations: Rich set of tools for creating fluid UIs
- SvelteKit: A full-featured framework for building Svelte apps
By leveraging Svelte's unique features, you can build fast, modern, and maintainable web applications with a great developer experience.