Skip to content

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

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>&copy; 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

# Install Vitest
npm install --save-dev vitest @testing-library/svelte
// 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

# Install Playwright
npm install --save-dev @playwright/test
// 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.