Documentation Index Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
Svelte is a radical new approach to building user interfaces. Instead of using a runtime library, Svelte is a compiler that converts your components into efficient JavaScript at build time.
Why Svelte?
Svelte takes a fundamentally different approach from React and Vue. Instead of shipping a framework to the browser, Svelte compiles your components into highly optimized vanilla JavaScript at build time. This results in incredibly small bundle sizes and blazing-fast performance.
Key strengths:
No virtual DOM : Direct DOM manipulation, faster updates
Truly reactive : Assignments trigger updates automatically
Compile-time framework : Most work done at build time, not runtime
Minimal boilerplate : Less code to write than React/Vue
Built-in animations : Transitions and animations included
Smallest bundles : Often 70-90% smaller than React equivalents
Great DX : Intuitive syntax, excellent error messages
Svelte vs Other Frameworks
Feature Svelte React Vue 3 Angular Bundle Size ~3 KB ~44 KB ~34 KB ~167 KB Runtime None (compiled) Virtual DOM Virtual DOM Zone.js + RxJS Reactivity Compile-time Hooks Proxy-based RxJS Observables Boilerplate Minimal Moderate Moderate High Learning Curve Gentle Moderate Gentle Steep TypeScript Excellent Excellent Excellent Built-in Animations Built-in Third-party Third-party Built-in SEO/SSR SvelteKit Next.js Nuxt Universal Ecosystem Growing Largest Large Large Best For Fast, small apps Large ecosystems Progressive apps Enterprise
Svelte + Mizu vs Standalone Svelte
Aspect Svelte + Mizu Standalone Svelte (SPA) Backend Go (embedded FS) Separate API server Deployment Single binary Frontend + backend separate Development Mizu proxy + Vite HMR Vite dev server only API Calls Same origin (/api/*) CORS required Build make build (Go + Svelte)npm run build onlyProduction ./bin/servernginx/CDN + API server Type Safety Go backend + TS frontend TS frontend only
Quick Start
Create a new Svelte project with the CLI:
mizu new ./my-svelte-app --template frontend/svelte
cd my-svelte-app
make dev
Visit http://localhost:3000 to see your app!
Architecture
Development Mode
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Svelte App (HMR enabled, compiled components) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌──────────┐ │ │
│ │ │ Router │→ │ Pages │→ │ API Call │ │ │
│ │ └─────────┘ └─────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↑ HMR WebSocket ↓ /api/tasks │
└─────────┼──────────────────────────┼────────────────────────┘
│ │
┌─────────┼──────────────────────────┼────────────────────────┐
│ Mizu Server (:3000) │ │
│ │ ↓ │
│ ┌────┴─────────┐ ┌────────────┐ │
│ │ Proxy to │ │ API Routes │ │
│ │ Vite (:5173) │ │ (Go) │ │
│ └──────────────┘ └────────────┘ │
│ ↑ │
└─────────┼────────────────────────────────────────────────────┘
│
┌─────────┼────────────────────────────────────────────────────┐
│ Vite Dev Server (:5173) │
│ ┌──────────────┐ ┌─────────────┐ │
│ │ Svelte │ │ HMR Engine │ │
│ │ Compiler │ │ │ │
│ └──────────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────────┘
Production Mode
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Svelte App (compiled to vanilla JS, minified) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌──────────┐ │ │
│ │ │ Router │→ │ Pages │→ │ API Call │ │ │
│ │ └─────────┘ └─────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↑ index.html + assets ↓ /api/tasks │
└─────────┼──────────────────────────┼────────────────────────┘
│ │
┌─────────┼──────────────────────────┼────────────────────────┐
│ Mizu Server (single binary) │ │
│ │ ↓ │
│ ┌────┴─────────┐ ┌────────────┐ │
│ │ Embedded FS │ │ API Routes │ │
│ │ (dist/...) │ │ (Go) │ │
│ └──────────────┘ └────────────┘ │
│ ←──────────────────────────────────────────────────────── │
│ Static files served from Go binary (//go:embed all:dist) │
└──────────────────────────────────────────────────────────────┘
Project Structure
my-svelte-app/
├── cmd/
│ └── server/
│ └── main.go # Entry point
├── app/
│ └── server/
│ ├── app.go # Mizu app setup
│ ├── config.go # Configuration
│ └── routes.go # API routes
├── frontend/ # Svelte application
│ ├── src/
│ │ ├── main.ts # App entry point
│ │ ├── App.svelte # Root component
│ │ ├── components/
│ │ │ └── Layout.svelte # Layout component
│ │ ├── pages/
│ │ │ ├── Home.svelte # Home page
│ │ │ └── About.svelte # About page
│ │ ├── stores/ # Svelte stores
│ │ └── styles/
│ │ └── index.css # Global styles
│ ├── public/
│ ├── index.html
│ ├── package.json
│ ├── vite.config.ts
│ ├── svelte.config.js
│ └── tsconfig.json
├── dist/ # Built files
└── Makefile
Backend Setup
Standard Mizu setup with auto-detection:
package server
import (
" embed "
" io/fs "
" github.com/go-mizu/mizu "
" github.com/go-mizu/mizu/frontend "
)
//go:embed all:../../dist
var distFS embed . FS
func New ( cfg * Config ) * mizu . App {
app := mizu . New ()
// API routes
setupRoutes ( app )
// Frontend middleware
dist , _ := fs . Sub ( distFS , "dist" )
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeAuto ,
FS : dist ,
DevServer : "http://localhost:" + cfg . DevPort ,
IgnorePaths : [] string { "/api" },
}))
return app
}
How it works:
//go:embed all:../../dist embeds the compiled Svelte app into the Go binary
frontend.ModeAuto switches between dev (proxy) and production (embedded FS)
DevServer proxies to Vite during development
Svelte compiles to vanilla JS, so bundle is tiny
API Routes Example
// app/server/routes.go
package server
import " github.com/go-mizu/mizu "
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
func setupRoutes ( app * mizu . App ) {
// GET /api/tasks
app . GET ( "/api/tasks" , func ( c * mizu . Ctx ) error {
tasks := [] Task {
{ ID : 1 , Title : "Learn Svelte" , Done : true },
{ ID : 2 , Title : "Build app with Mizu" , Done : false },
}
return c . JSON ( tasks )
})
// POST /api/tasks
app . POST ( "/api/tasks" , func ( c * mizu . Ctx ) error {
var task Task
if err := c . BodyParser ( & task ); err != nil {
return err
}
task . ID = 3 // In real app, generate from DB
return c . Status ( 201 ). JSON ( task )
})
// DELETE /api/tasks/:id
app . DELETE ( "/api/tasks/:id" , func ( c * mizu . Ctx ) error {
return c . Status ( 204 ). Send ( nil )
})
}
Frontend Setup
frontend/src/main.ts
import App from './App.svelte'
import './styles/index.css'
const app = new App ({
target: document . getElementById ( 'app' ) ! ,
})
export default app
frontend/src/App.svelte
< script lang = "ts" >
import Layout from './components/Layout.svelte'
import Home from './pages/Home.svelte'
import About from './pages/About.svelte'
let currentPage = 'home'
function navigate ( page : string ) {
currentPage = page
window . history . pushState ({}, '' , `/ ${ page === 'home' ? '' : page } ` )
}
// Handle browser back/forward
window . addEventListener ( 'popstate' , () => {
const path = window . location . pathname . slice ( 1 ) || 'home'
currentPage = path
})
</ script >
< Layout >
{# if currentPage === 'home' }
< Home on : navigate = { ( e ) => navigate ( e . detail ) } />
{: else if currentPage === 'about' }
< About on : navigate = { ( e ) => navigate ( e . detail ) } />
{/ if }
</ Layout >
frontend/src/components/Layout.svelte
< script lang = "ts" >
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher ()
function navigate ( page : string ) {
dispatch ( 'navigate' , page )
}
</ script >
< div class = "app" >
< nav >
< a href = "/" on : click | preventDefault = { () => navigate ( 'home' ) } > Home </ a >
< a href = "/about" on : click | preventDefault = { () => navigate ( 'about' ) } > About </ a >
</ nav >
< main >
< slot />
</ main >
< footer >
< p > Built with Mizu and Svelte </ p >
</ footer >
</ div >
< style >
nav {
display : flex ;
gap : 1 rem ;
padding : 1 rem ;
background : #f5f5f5 ;
}
nav a {
color : #ff3e00 ;
text-decoration : none ;
}
nav a :hover {
text-decoration : underline ;
}
main {
padding : 2 rem ;
}
footer {
margin-top : 4 rem ;
text-align : center ;
color : #666 ;
}
</ style >
frontend/src/pages/Home.svelte
< script lang = "ts" >
import { onMount } from 'svelte'
interface User {
id : number
name : string
email : string
}
let users : User [] = []
let loading = true
let error : string | null = null
onMount ( async () => {
try {
const response = await fetch ( '/api/users' )
if ( ! response . ok ) throw new Error ( 'Failed to fetch users' )
users = await response . json ()
} catch ( err ) {
error = err instanceof Error ? err . message : 'Unknown error'
} finally {
loading = false
}
})
</ script >
< div >
< h1 > Users </ h1 >
{# if loading }
< p > Loading... </ p >
{: else if error }
< p class = "error" > Error: { error } </ p >
{: else }
< ul >
{# each users as user ( user . id )}
< li > { user . name } ( { user . email } ) </ li >
{/ each }
</ ul >
{/ if }
</ div >
< style >
.error {
color : red ;
}
ul {
list-style : none ;
padding : 0 ;
}
li {
padding : 0.5 rem ;
border-bottom : 1 px solid #eee ;
}
</ style >
Svelte Reactivity
Svelte’s reactivity is built into the language itself. Assignments are reactive.
Basic Reactivity
< script lang = "ts" >
let count = 0
// Assignment triggers update
function increment () {
count = count + 1 // or count++
}
</ script >
< p > { count } </ p >
< button on : click = { increment } > Increment </ button >
How it works: Svelte’s compiler analyzes your code and inserts update calls wherever variables are reassigned.
Reactive Declarations ($:)
Use $: to create reactive statements that automatically re-run when dependencies change.
< script lang = "ts" >
let count = 0
// Reactive declaration - updates when count changes
$ : doubled = count * 2
// Reactive statement - runs when count changes
$ : if ( count > 10 ) {
console . log ( 'Count is getting high!' )
}
// Reactive block - multiple statements
$ : {
console . log ( 'Count changed to' , count )
console . log ( 'Doubled is' , doubled )
}
</ script >
< p > Count: { count } </ p >
< p > Doubled: { doubled } </ p >
< button on : click = { () => count ++ } > Increment </ button >
Reactive Dependencies
Svelte automatically tracks dependencies:
< script lang = "ts" >
let firstName = 'John'
let lastName = 'Doe'
// Automatically re-runs when firstName or lastName change
$ : fullName = ` ${ firstName } ${ lastName } `
// Multiple dependencies
$ : initials = ` ${ firstName [ 0 ] }${ lastName [ 0 ] } `
</ script >
< input bind : value = { firstName } />
< input bind : value = { lastName } />
< p > Full name: { fullName } </ p >
< p > Initials: { initials } </ p >
Array and Object Reactivity
Important: Assignments trigger updates, but mutating methods (push, pop, etc.) don’t.
< script lang = "ts" >
let numbers = [ 1 , 2 , 3 ]
// ❌ Does NOT trigger update
function addWrong () {
numbers . push ( 4 )
}
// ✅ Triggers update (assignment)
function addCorrect () {
numbers = [ ... numbers , 4 ]
}
// ✅ Alternative with assignment
function addAlternative () {
numbers . push ( 4 )
numbers = numbers // Trigger update
}
// Objects work the same way
let user = { name: 'Alice' , age: 30 }
// ❌ Does NOT trigger update
function updateAgeWrong () {
user . age = 31
}
// ✅ Triggers update
function updateAgeCorrect () {
user = { ... user , age: 31 }
}
</ script >
< p > Numbers: { numbers . join ( ', ' ) } </ p >
< button on : click = { addCorrect } > Add Number </ button >
Component Communication
Props
Pass data from parent to child using export let.
<!-- UserCard.svelte -->
< script lang = "ts" >
interface User {
id : number
name : string
email : string
}
// Exported variables become props
export let user : User
export let showEmail = true // Default value
</ script >
< div class = "card" >
< h3 > { user . name } </ h3 >
{# if showEmail }
< p > { user . email } </ p >
{/ if }
</ div >
< style >
.card {
padding : 1 rem ;
border : 1 px solid #ddd ;
border-radius : 8 px ;
}
</ style >
<!-- Parent.svelte -->
< script lang = "ts" >
import UserCard from './UserCard.svelte'
const user = { id: 1 , name: 'Alice' , email: 'alice@example.com' }
</ script >
< UserCard { user } showEmail = { true } />
<!-- Shorthand when variable name matches prop name: -->
< UserCard { user } />
Events
Use createEventDispatcher to communicate from child to parent.
<!-- Counter.svelte -->
< script lang = "ts" >
import { createEventDispatcher } from 'svelte'
export let count = 0
const dispatch = createEventDispatcher <{
increment : number
decrement : number
}>()
function handleIncrement () {
count ++
dispatch ( 'increment' , count )
}
function handleDecrement () {
count --
dispatch ( 'decrement' , count )
}
</ script >
< div >
< p > Count: { count } </ p >
< button on : click = { handleIncrement } > + </ button >
< button on : click = { handleDecrement } > - </ button >
</ div >
<!-- Parent.svelte -->
< script lang = "ts" >
import Counter from './Counter.svelte'
function handleIncrement ( event : CustomEvent < number >) {
console . log ( 'Count incremented to' , event . detail )
}
</ script >
< Counter on : increment = { handleIncrement } on : decrement = { ( e ) => console . log ( e . detail ) } />
Event Forwarding
Forward events from child components:
<!-- Button.svelte -->
< button on : click >
< slot />
</ button >
<!-- Parent.svelte -->
< script lang = "ts" >
import Button from './Button.svelte'
</ script >
<!-- Event automatically forwarded -->
< Button on : click = { () => console . log ( 'Clicked!' ) } >
Click me
</ Button >
Context API
Share data between components without prop drilling.
<!-- Parent.svelte -->
< script lang = "ts" >
import { setContext } from 'svelte'
import Child from './Child.svelte'
const theme = {
primary: '#ff3e00' ,
background: '#ffffff'
}
setContext ( 'theme' , theme )
</ script >
< Child />
<!-- Child.svelte (deeply nested) -->
< script lang = "ts" >
import { getContext } from 'svelte'
interface Theme {
primary : string
background : string
}
const theme = getContext < Theme >( 'theme' )
</ script >
< div style = "color: { theme . primary } ; background: { theme . background } " >
Themed content
</ div >
Slots
Pass markup from parent to child.
<!-- Card.svelte -->
< div class = "card" >
< header >
< slot name = "header" >
< h2 > Default Header </ h2 >
</ slot >
</ header >
< main >
< slot /> <!-- Default slot -->
</ main >
< footer >
< slot name = "footer" />
</ footer >
</ div >
<!-- Parent.svelte -->
< script lang = "ts" >
import Card from './Card.svelte'
</ script >
< Card >
< h2 slot = "header" > Custom Header </ h2 >
<!-- Default slot content -->
< p > This is the card body </ p >
< div slot = "footer" >
< button > OK </ button >
< button > Cancel </ button >
</ div >
</ Card >
Slot Props
Pass data from child to parent through slots:
<!-- List.svelte -->
< script lang = "ts" >
export let items : any []
</ script >
< ul >
{# each items as item , index ( item . id )}
< li >
<!-- Expose item and index to parent -->
< slot { item } { index } />
</ li >
{/ each }
</ ul >
<!-- Parent.svelte -->
< script lang = "ts" >
import List from './List.svelte'
const users = [
{ id: 1 , name: 'Alice' },
{ id: 2 , name: 'Bob' }
]
</ script >
< List items = { users } let : item let : index >
< strong > { index + 1 } . </ strong > { item . name }
</ List >
Svelte Stores
Stores provide a way to share state across components.
Writable Stores
// stores/counter.ts
import { writable } from 'svelte/store'
export const count = writable ( 0 )
// Methods
export function increment () {
count . update ( n => n + 1 )
}
export function decrement () {
count . update ( n => n - 1 )
}
export function reset () {
count . set ( 0 )
}
<!-- Component.svelte -->
< script lang = "ts" >
import { count , increment , decrement , reset } from './stores/counter'
</ script >
<!-- $ prefix auto-subscribes -->
< p > Count: { $ count } </ p >
< button on : click = { increment } > + </ button >
< button on : click = { decrement } > - </ button >
< button on : click = { reset } > Reset </ button >
Readable Stores
For read-only values (e.g., time, mouse position):
// stores/time.ts
import { readable } from 'svelte/store'
export const time = readable ( new Date (), ( set ) => {
const interval = setInterval (() => {
set ( new Date ())
}, 1000 )
// Cleanup function
return () => clearInterval ( interval )
})
< script lang = "ts" >
import { time } from './stores/time'
</ script >
< p > Current time: { $ time . toLocaleTimeString () } </ p >
Derived Stores
Create stores derived from other stores:
// stores/user.ts
import { writable , derived } from 'svelte/store'
interface User {
id : number
name : string
email : string
}
export const users = writable < User []>([])
// Derived from users store
export const userCount = derived ( users , $users => $users . length )
export const userNames = derived ( users , $users => $users . map ( u => u . name ))
// Derived from multiple stores
import { count } from './counter'
export const info = derived (
[ users , count ],
([ $users , $count ]) => ` ${ $users . length } users, count: ${ $count } `
)
< script lang = "ts" >
import { users , userCount , userNames } from './stores/user'
</ script >
< p > Total users: { $ userCount } </ p >
< p > Names: { $ userNames . join ( ', ' ) } </ p >
Custom Stores
Create stores with custom methods:
// stores/tasks.ts
import { writable } from 'svelte/store'
interface Task {
id : number
title : string
done : boolean
}
function createTaskStore () {
const { subscribe , set , update } = writable < Task []>([])
return {
subscribe ,
add : ( title : string ) => update ( tasks => {
const newTask = {
id: Date . now (),
title ,
done: false
}
return [ ... tasks , newTask ]
}),
toggle : ( id : number ) => update ( tasks =>
tasks . map ( task =>
task . id === id ? { ... task , done: ! task . done } : task
)
),
remove : ( id : number ) => update ( tasks =>
tasks . filter ( task => task . id !== id )
),
reset : () => set ([])
}
}
export const tasks = createTaskStore ()
< script lang = "ts" >
import { tasks } from './stores/tasks'
let newTaskTitle = ''
function addTask () {
if ( newTaskTitle . trim ()) {
tasks . add ( newTaskTitle )
newTaskTitle = ''
}
}
</ script >
< input bind : value = { newTaskTitle } on : keydown = { ( e ) => e . key === 'Enter' && addTask () } />
< button on : click = { addTask } > Add Task </ button >
< ul >
{# each $ tasks as task ( task . id )}
< li >
< input type = "checkbox" checked = { task . done } on : change = { () => tasks . toggle ( task . id ) } />
< span class : done = { task . done } > { task . title } </ span >
< button on : click = { () => tasks . remove ( task . id ) } > Delete </ button >
</ li >
{/ each }
</ ul >
< style >
.done {
text-decoration : line-through ;
opacity : 0.6 ;
}
</ style >
Store Contracts
Type-safe store subscription:
import type { Writable } from 'svelte/store'
import { writable } from 'svelte/store'
interface UserStore extends Writable < User []> {
fetchUsers : () => Promise < void >
}
export const users : UserStore = (() => {
const { subscribe , set , update } = writable < User []>([])
return {
subscribe ,
set ,
update ,
fetchUsers : async () => {
const res = await fetch ( '/api/users' )
const data = await res . json ()
set ( data )
}
}
})()
Routing
Svelte doesn’t include routing by default. Here are your options:
Option 1: Simple Routing (Manual)
We’ve seen this in the App.svelte example above.
Option 2: svelte-spa-router
cd frontend
npm install svelte-spa-router
Basic usage:
<!-- App.svelte -->
< script lang = "ts" >
import Router from 'svelte-spa-router'
import Home from './pages/Home.svelte'
import About from './pages/About.svelte'
import UserDetail from './pages/UserDetail.svelte'
import NotFound from './pages/NotFound.svelte'
const routes = {
'/' : Home ,
'/about' : About ,
'/users/:id' : UserDetail ,
'*' : NotFound , // 404 catch-all
}
</ script >
< Router { routes } />
Dynamic Routes with Params
<!-- pages/UserDetail.svelte -->
< script lang = "ts" >
export let params : { id : string }
let user = null
let loading = true
$ : if ( params ?. id ) {
fetchUser ( params . id )
}
async function fetchUser ( id : string ) {
loading = true
const res = await fetch ( `/api/users/ ${ id } ` )
user = await res . json ()
loading = false
}
</ script >
< div >
{# if loading }
< p > Loading user... </ p >
{: else if user }
< h1 > { user . name } </ h1 >
< p > { user . email } </ p >
{: else }
< p > User not found </ p >
{/ if }
</ div >
Programmatic Navigation
< script lang = "ts" >
import { push , pop , replace } from 'svelte-spa-router'
function goToUser ( id : number ) {
push ( `/users/ ${ id } ` )
}
function goBack () {
pop ()
}
function replaceRoute () {
replace ( '/home' )
}
</ script >
< button on : click = { () => goToUser ( 123 ) } > View User 123 </ button >
< button on : click = { goBack } > Go Back </ button >
Route Guards
< script lang = "ts" >
import Router from 'svelte-spa-router'
import { wrap } from 'svelte-spa-router/wrap'
function authGuard ( detail ) {
const token = localStorage . getItem ( 'token' )
if ( ! token ) {
return false // Prevent navigation
}
return true
}
const routes = {
'/' : Home ,
'/login' : Login ,
'/dashboard' : wrap ({
component: Dashboard ,
conditions: [ authGuard ]
})
}
</ script >
< Router { routes } />
Option 3: SvelteKit
For full-featured routing with SSR, use SvelteKit :
File-based routing
Server-side rendering
Static site generation
API routes
Advanced features
Lifecycle Hooks
Svelte provides lifecycle functions from the svelte package.
< script lang = "ts" >
import { onMount , onDestroy , beforeUpdate , afterUpdate , tick } from 'svelte'
let count = 0
// Runs after component is first rendered to DOM
onMount (() => {
console . log ( 'Component mounted' )
// Return cleanup function
return () => {
console . log ( 'Cleanup on unmount' )
}
})
// Runs before component is destroyed
onDestroy (() => {
console . log ( 'Component will be destroyed' )
})
// Runs before DOM is updated
beforeUpdate (() => {
console . log ( 'About to update DOM' )
})
// Runs after DOM is updated
afterUpdate (() => {
console . log ( 'DOM updated' )
})
async function incrementAndLog () {
count ++
// Wait for DOM to update
await tick ()
console . log ( 'DOM has updated with new count' )
}
</ script >
< p > { count } </ p >
< button on : click = { incrementAndLog } > Increment </ button >
Lifecycle order:
Component created
onMount() - After initial render
beforeUpdate() - Before each update
afterUpdate() - After each update
onDestroy() - Before removal
Bindings
Svelte provides powerful two-way bindings.
< script lang = "ts" >
let text = ''
let number = 0
let checked = false
let group : string [] = []
let radio = ''
let selected = ''
let files : FileList
</ script >
<!-- Text input -->
< input bind : value = { text } />
<!-- Number input -->
< input type = "number" bind : value = { number } />
<!-- Checkbox -->
< input type = "checkbox" bind : checked />
<!-- Checkbox group (array) -->
< input type = "checkbox" value = "a" bind : group />
< input type = "checkbox" value = "b" bind : group />
<!-- Radio buttons -->
< input type = "radio" value = "yes" bind : group = { radio } />
< input type = "radio" value = "no" bind : group = { radio } />
<!-- Select -->
< select bind : value = { selected } >
< option value = "a" > A </ option >
< option value = "b" > B </ option >
</ select >
<!-- File input -->
< input type = "file" bind : files />
Element Bindings (bind:this)
Get a reference to a DOM element:
< script lang = "ts" >
let canvas : HTMLCanvasElement
function draw () {
const ctx = canvas . getContext ( '2d' )
if ( ctx ) {
ctx . fillRect ( 0 , 0 , 100 , 100 )
}
}
</ script >
< canvas bind : this = { canvas } width = " 200 " height = " 200 " ></ canvas >
< button on : click = { draw } > Draw </ button >
Component Bindings
Bind to component props:
<!-- Input.svelte -->
< script lang = "ts" >
export let value = ''
</ script >
< input bind : value />
<!-- Parent.svelte -->
< script lang = "ts" >
import Input from './Input.svelte'
let text = 'Hello'
</ script >
<!-- Two-way binding to child component -->
< Input bind : value = { text } />
< p > Text: { text } </ p >
< script lang = "ts" >
let clientWidth : number
let scrollY : number
</ script >
< div bind : clientWidth >
Width: { clientWidth } px
</ div >
< svelte : window bind : scrollY />
< p > Scrolled: { scrollY } px </ p >
Logic Blocks
{#if} blocks
< script lang = "ts" >
let count = 0
</ script >
{# if count === 0 }
< p > Count is zero </ p >
{: else if count < 5 }
< p > Count is less than 5 </ p >
{: else }
< p > Count is { count } </ p >
{/ if }
{#each} blocks
< script lang = "ts" >
let items = [
{ id: 1 , name: 'Apple' },
{ id: 2 , name: 'Banana' },
{ id: 3 , name: 'Cherry' }
]
</ script >
<!-- With key for efficient updates -->
{# each items as item ( item . id )}
< p > { item . name } </ p >
{/ each }
<!-- With index -->
{# each items as item , index ( item . id )}
< p > { index + 1 } . { item . name } </ p >
{/ each }
<!-- Empty state -->
{# each items as item }
< p > { item . name } </ p >
{: else }
< p > No items found </ p >
{/ each }
{#await} blocks
Handle promises declaratively:
< script lang = "ts" >
async function fetchUser ( id : number ) {
const res = await fetch ( `/api/users/ ${ id } ` )
return res . json ()
}
let promise = fetchUser ( 1 )
</ script >
{# await promise }
< p > Loading... </ p >
{: then user }
< p > { user . name } </ p >
{: catch error }
< p class = "error" > { error . message } </ p >
{/ await }
<!-- Or just handle then/catch -->
{# await promise then user }
< p > { user . name } </ p >
{/ await }
{#key} blocks
Re-render when value changes:
< script lang = "ts" >
let value = 0
</ script >
<!-- Component re-mounts when value changes -->
{# key value }
< div transition : fade >
Value: { value }
</ div >
{/ key }
< button on : click = { () => value ++ } > Change </ button >
Special Elements
<svelte:self>
Recursively render a component:
<!-- Tree.svelte -->
< script lang = "ts" >
export let node : { name : string , children ?: any [] }
</ script >
< li >
{ node . name }
{# if node . children }
< ul >
{# each node . children as child }
< svelte : self node = { child } />
{/ each }
</ ul >
{/ if }
</ li >
<svelte:component>
Dynamically render different components:
< script lang = "ts" >
import ComponentA from './ComponentA.svelte'
import ComponentB from './ComponentB.svelte'
let selected = 'A'
$ : component = selected === 'A' ? ComponentA : ComponentB
</ script >
< select bind : value = { selected } >
< option value = "A" > Component A </ option >
< option value = "B" > Component B </ option >
</ select >
< svelte : component this = { component } />
<svelte:window>
Bind to window events and properties:
< script lang = "ts" >
let scrollY : number
let innerWidth : number
function handleKeydown ( event : KeyboardEvent ) {
console . log ( 'Key pressed:' , event . key )
}
</ script >
< svelte : window
bind : scrollY
bind : innerWidth
on : keydown = { handleKeydown }
/>
< p > Scroll: { scrollY } px, Width: { innerWidth } px </ p >
<svelte:body>
Bind to document.body events:
< script lang = "ts" >
function handleClick ( event : MouseEvent ) {
console . log ( 'Clicked at' , event . clientX , event . clientY )
}
</ script >
< svelte : body on : click = { handleClick } />
<svelte:head>
Add elements to document.head:
< svelte : head >
< title > My Page Title </ title >
< meta name = "description" content = "Page description" />
< link rel = "stylesheet" href = "/custom.css" />
</ svelte : head >
Transitions and Animations
Svelte has built-in transitions and animations.
Built-in Transitions
< script lang = "ts" >
import { fade , fly , slide , scale , blur } from 'svelte/transition'
let visible = true
</ script >
< button on : click = { () => visible = ! visible } > Toggle </ button >
{# if visible }
< div transition : fade > Fades in and out </ div >
< div transition : fly = { { y: 200 , duration: 300 } } >
Flies in and out
</ div >
< div transition : slide > Slides in and out </ div >
< div transition : scale = { { start: 0.5 } } > Scales in and out </ div >
< div transition : blur > Blurs in and out </ div >
{/ if }
Separate In/Out Transitions
< script lang = "ts" >
import { fade , fly } from 'svelte/transition'
let visible = true
</ script >
{# if visible }
< div in : fly = { { y: 200 } } out : fade >
Flies in, fades out
</ div >
{/ if }
Custom Transitions
< script lang = "ts" >
import { cubicOut } from 'svelte/easing'
function spin ( node : Element , { duration = 400 }) {
return {
duration ,
css : ( t : number ) => {
const eased = cubicOut ( t )
return `
transform: rotate( ${ eased * 360 } deg);
opacity: ${ t } ;
`
}
}
}
let visible = true
</ script >
{# if visible }
< div transition : spin = { { duration: 600 } } >
Spins in and out
</ div >
{/ if }
Transition Events
< script lang = "ts" >
import { fade } from 'svelte/transition'
function handleIntroStart () {
console . log ( 'Transition started' )
}
function handleIntroEnd () {
console . log ( 'Transition ended' )
}
</ script >
< div
transition : fade
on : introstart = { handleIntroStart }
on : introend = { handleIntroEnd }
on : outrostart = { () => console . log ( 'Outro started' ) }
on : outroend = { () => console . log ( 'Outro ended' ) }
>
Content
</ div >
Animations (flip)
Animate element positions in lists:
< script lang = "ts" >
import { flip } from 'svelte/animate'
import { fade } from 'svelte/transition'
let items = [ 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: 300 } } transition : fade >
Item { item }
</ div >
{/ each }
Motion (Tweened and Spring)
Animate values over time.
Tweened
< script lang = "ts" >
import { tweened } from 'svelte/motion'
import { cubicOut } from 'svelte/easing'
const progress = tweened ( 0 , {
duration: 400 ,
easing: cubicOut
})
function reset () {
progress . set ( 0 )
}
function fill () {
progress . set ( 100 )
}
</ script >
< progress value = { $ progress / 100 } ></ progress >
< p > { $ progress . toFixed ( 0 ) } % </ p >
< button on : click = { reset } > Reset </ button >
< button on : click = { fill } > Fill </ button >
Spring
Physics-based animation:
< script lang = "ts" >
import { spring } from 'svelte/motion'
const coords = spring ({ x: 50 , y: 50 }, {
stiffness: 0.1 ,
damping: 0.25
})
function handleMousemove ( event : MouseEvent ) {
coords . set ({ x: event . clientX , y: event . clientY })
}
</ script >
< svelte : window on : mousemove = { handleMousemove } />
< div
style = "
position: absolute;
left: { $ coords . x } px;
top: { $ coords . y } px;
width: 20px;
height: 20px;
background: #ff3e00;
border-radius: 50%;
"
/>
Actions
Reusable DOM manipulation functions.
Basic Action
< script lang = "ts" >
function tooltip ( node : HTMLElement , text : string ) {
const tooltip = document . createElement ( 'div' )
tooltip . textContent = text
tooltip . style . cssText = `
position: absolute;
background: black;
color: white;
padding: 4px 8px;
border-radius: 4px;
display: none;
`
document . body . appendChild ( tooltip )
function handleMouseenter () {
tooltip . style . display = 'block'
const rect = node . getBoundingClientRect ()
tooltip . style . left = ` ${ rect . left } px`
tooltip . style . top = ` ${ rect . bottom + 5 } px`
}
function handleMouseleave () {
tooltip . style . display = 'none'
}
node . addEventListener ( 'mouseenter' , handleMouseenter )
node . addEventListener ( 'mouseleave' , handleMouseleave )
return {
destroy () {
tooltip . remove ()
node . removeEventListener ( 'mouseenter' , handleMouseenter )
node . removeEventListener ( 'mouseleave' , handleMouseleave )
}
}
}
</ script >
< button use : tooltip = "This is a tooltip" >
Hover me
</ button >
Action with Parameters
< script lang = "ts" >
function longpress ( node : HTMLElement , duration = 500 ) {
let timer : ReturnType < typeof setTimeout >
function handleMousedown () {
timer = setTimeout (() => {
node . dispatchEvent ( new CustomEvent ( 'longpress' ))
}, duration )
}
function handleMouseup () {
clearTimeout ( timer )
}
node . addEventListener ( 'mousedown' , handleMousedown )
node . addEventListener ( 'mouseup' , handleMouseup )
return {
update ( newDuration : number ) {
duration = newDuration
},
destroy () {
clearTimeout ( timer )
node . removeEventListener ( 'mousedown' , handleMousedown )
node . removeEventListener ( 'mouseup' , handleMouseup )
}
}
}
</ script >
< button use : longpress = { 1000 } on : longpress = { () => alert ( 'Long pressed!' ) } >
Press and hold
</ button >
Class and Style Directives
Class Directive
< script lang = "ts" >
let active = false
let error = false
</ script >
<!-- class:name={condition} -->
< button class : active class : error >
Button
</ button >
<!-- Shorthand when variable name matches class name -->
< button class : active = { active } > Button </ button >
< style >
.active {
background : blue ;
}
.error {
border : 2 px solid red ;
}
</ style >
Style Directive
< script lang = "ts" >
let color = '#ff3e00'
let size = 16
</ script >
<!-- style:property={value} -->
< p style : color style : font-size = " { size } px" >
Styled text
</ p >
<!-- Shorthand -->
< p style : color = { color } > Text </ p >
<!-- With important -->
< p style : color | important = { color } > Text </ p >
Vite Configuration
frontend/vite.config.ts
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import path from 'path'
export default defineConfig ({
plugins: [ svelte ()] ,
resolve: {
alias: {
'@' : path . resolve ( __dirname , './src' ),
'@components' : path . resolve ( __dirname , './src/components' ),
'@pages' : path . resolve ( __dirname , './src/pages' ),
'@stores' : path . resolve ( __dirname , './src/stores' ),
},
} ,
server: {
port: 5173 ,
strictPort: true ,
hmr: {
clientPort: 3000 , // Mizu's port for HMR
},
} ,
build: {
outDir: '../dist' ,
emptyOutDir: true ,
sourcemap: false ,
rollupOptions: {
output: {
manualChunks: {
'svelte-vendor' : [ 'svelte' ],
},
},
},
} ,
})
frontend/svelte.config.js
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
preprocess: vitePreprocess () ,
compilerOptions: {
// Enable CSS hash for production
cssHash : ({ hash , css }) => `svelte- ${ hash ( css ) } `
}
}
Immutable Data
Tell Svelte your data is immutable for performance:
< svelte : options immutable = { true } />
< script lang = "ts" >
export let data : any []
// Svelte will use reference equality (===) instead of deep comparison
</ script >
Component Options
< svelte : options
immutable = { true }
accessors = { false }
namespace = "svg"
/>
Styling
Component Styles (Scoped)
Styles are scoped by default:
< div class = "card" >
< h2 > Title </ h2 >
< p > Content </ p >
</ div >
< style >
.card {
border : 1 px solid #ddd ;
padding : 1 rem ;
border-radius : 8 px ;
}
/* Only affects this component */
h2 {
color : #ff3e00 ;
}
</ style >
Global Styles
Use :global() modifier:
< style >
:global( body ) {
margin : 0 ;
font-family : sans-serif ;
}
.container :global( p ) {
/* Targets all <p> inside .container */
margin : 1 rem 0 ;
}
</ style >
Tailwind CSS
Install Tailwind:
cd frontend
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Configure tailwind.config.js:
export default {
content: [
"./index.html" ,
"./src/**/*.{svelte,js,ts}" ,
] ,
theme: {
extend: {
colors: {
primary: '#ff3e00' ,
},
},
} ,
plugins: [] ,
}
Add to src/styles/index.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-primary {
@ apply bg-primary text-white px- 4 py- 2 rounded hover :bg-orange-600 transition;
}
}
Use in components:
< div class = "container mx-auto px-4" >
< h1 class = "text-4xl font-bold text-primary" > Hello Svelte! </ h1 >
< button class = "btn-primary" >
Click me
</ button >
</ div >
Complete Real-World Example: Task Manager
Let’s build a complete task management app with Svelte stores and routing.
Backend (Go)
// app/server/routes.go
package server
import (
" sync "
" github.com/go-mizu/mizu "
)
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Status string `json:"status"` // "pending", "in-progress", "completed"
Priority string `json:"priority"` // "low", "medium", "high"
}
var (
tasks = [] Task {}
nextID = 1
tasksMu sync . RWMutex
)
func setupRoutes ( app * mizu . App ) {
// GET /api/tasks
app . GET ( "/api/tasks" , func ( c * mizu . Ctx ) error {
tasksMu . RLock ()
defer tasksMu . RUnlock ()
return c . JSON ( tasks )
})
// POST /api/tasks
app . POST ( "/api/tasks" , func ( c * mizu . Ctx ) error {
var task Task
if err := c . BodyParser ( & task ); err != nil {
return c . Status ( 400 ). JSON ( map [ string ] string { "error" : "Invalid request" })
}
tasksMu . Lock ()
task . ID = nextID
nextID ++
tasks = append ( tasks , task )
tasksMu . Unlock ()
return c . Status ( 201 ). JSON ( task )
})
// PUT /api/tasks/:id
app . PUT ( "/api/tasks/:id" , func ( c * mizu . Ctx ) error {
id := c . ParamInt ( "id" )
var updates Task
if err := c . BodyParser ( & updates ); err != nil {
return c . Status ( 400 ). JSON ( map [ string ] string { "error" : "Invalid request" })
}
tasksMu . Lock ()
defer tasksMu . Unlock ()
for i , task := range tasks {
if task . ID == id {
updates . ID = id
tasks [ i ] = updates
return c . JSON ( updates )
}
}
return c . Status ( 404 ). JSON ( map [ string ] string { "error" : "Task not found" })
})
// DELETE /api/tasks/:id
app . DELETE ( "/api/tasks/:id" , func ( c * mizu . Ctx ) error {
id := c . ParamInt ( "id" )
tasksMu . Lock ()
defer tasksMu . Unlock ()
for i , task := range tasks {
if task . ID == id {
tasks = append ( tasks [: i ], tasks [ i + 1 :] ... )
return c . Status ( 204 ). Send ( nil )
}
}
return c . Status ( 404 ). JSON ( map [ string ] string { "error" : "Task not found" })
})
}
Frontend - Store
// src/stores/tasks.ts
import { writable , derived } from 'svelte/store'
export interface Task {
id : number
title : string
description : string
status : 'pending' | 'in-progress' | 'completed'
priority : 'low' | 'medium' | 'high'
}
export type TaskFilter = 'all' | 'pending' | 'in-progress' | 'completed'
function createTaskStore () {
const { subscribe , set , update } = writable < Task []>([])
const filter = writable < TaskFilter >( 'all' )
const loading = writable ( false )
return {
subscribe ,
filter ,
loading ,
filteredTasks: derived (
[{ subscribe }, filter ],
([ $tasks , $filter ]) => {
if ( $filter === 'all' ) return $tasks
return $tasks . filter ( t => t . status === $filter )
}
),
async fetchTasks () {
loading . set ( true )
try {
const res = await fetch ( '/api/tasks' )
const data = await res . json ()
set ( data )
} finally {
loading . set ( false )
}
},
async addTask ( task : Omit < Task , 'id' >) {
const res = await fetch ( '/api/tasks' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( task )
})
const newTask = await res . json ()
update ( tasks => [ ... tasks , newTask ])
},
async updateTask ( id : number , updates : Partial < Task >) {
let taskToUpdate : Task | undefined
update ( tasks => {
taskToUpdate = tasks . find ( t => t . id === id )
return tasks
})
if ( ! taskToUpdate ) return
const updated = { ... taskToUpdate , ... updates }
const res = await fetch ( `/api/tasks/ ${ id } ` , {
method: 'PUT' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( updated )
})
const newTask = await res . json ()
update ( tasks =>
tasks . map ( t => t . id === id ? newTask : t )
)
},
async deleteTask ( id : number ) {
await fetch ( `/api/tasks/ ${ id } ` , { method: 'DELETE' })
update ( tasks => tasks . filter ( t => t . id !== id ))
}
}
}
export const taskStore = createTaskStore ()
Frontend - Components
<!-- src/components/TaskForm.svelte -->
< script lang = "ts" >
import { taskStore } from '../stores/tasks'
let title = ''
let description = ''
let priority : 'low' | 'medium' | 'high' = 'medium'
async function handleSubmit () {
await taskStore . addTask ({
title ,
description ,
status: 'pending' ,
priority
})
title = ''
description = ''
priority = 'medium'
}
</ script >
< form on : submit | preventDefault = { handleSubmit } class = "task-form" >
< input bind : value = { title } placeholder = "Task title" required />
< textarea bind : value = { description } placeholder = "Description" />
< select bind : value = { priority } required >
< option value = "low" > Low Priority </ option >
< option value = "medium" > Medium Priority </ option >
< option value = "high" > High Priority </ option >
</ select >
< button type = "submit" > Add Task </ button >
</ form >
< style >
.task-form {
display : flex ;
flex-direction : column ;
gap : 1 rem ;
padding : 1 rem ;
background : #f5f5f5 ;
border-radius : 8 px ;
margin-bottom : 2 rem ;
}
input , textarea , select {
padding : 0.5 rem ;
border : 1 px solid #ddd ;
border-radius : 4 px ;
}
textarea {
min-height : 80 px ;
}
button {
background : #ff3e00 ;
color : white ;
padding : 0.75 rem ;
border : none ;
border-radius : 4 px ;
cursor : pointer ;
}
button :hover {
background : #e63900 ;
}
</ style >
<!-- src/components/TaskItem.svelte -->
< script lang = "ts" >
import { taskStore , type Task } from '../stores/tasks'
export let task : Task
function updateStatus ( e : Event ) {
const status = ( e . target as HTMLSelectElement ). value
taskStore . updateTask ( task . id , { status })
}
function deleteTask () {
if ( confirm ( 'Delete this task?' )) {
taskStore . deleteTask ( task . id )
}
}
</ script >
< div class = "task-item { task . priority } { task . status } " >
< div class = "task-content" >
< h3 > { task . title } </ h3 >
< p > { task . description } </ p >
< span class = "priority-badge" > { task . priority } </ span >
</ div >
< div class = "task-actions" >
< select value = { task . status } on : change = { updateStatus } >
< option value = "pending" > Pending </ option >
< option value = "in-progress" > In Progress </ option >
< option value = "completed" > Completed </ option >
</ select >
< button on : click = { deleteTask } class = "delete-btn" > Delete </ button >
</ div >
</ div >
< style >
.task-item {
display : flex ;
justify-content : space-between ;
padding : 1 rem ;
border : 1 px solid #ddd ;
border-radius : 8 px ;
margin-bottom : 1 rem ;
}
.task-item.high {
border-left : 4 px solid #ef4444 ;
}
.task-item.medium {
border-left : 4 px solid #f59e0b ;
}
.task-item.low {
border-left : 4 px solid #10b981 ;
}
.task-item.completed {
opacity : 0.6 ;
}
.task-item.completed h3 {
text-decoration : line-through ;
}
.priority-badge {
display : inline-block ;
padding : 0.25 rem 0.5 rem ;
background : #e5e7eb ;
border-radius : 4 px ;
font-size : 0.75 rem ;
text-transform : uppercase ;
}
.task-actions {
display : flex ;
gap : 0.5 rem ;
align-items : flex-start ;
}
select {
padding : 0.5 rem ;
border : 1 px solid #ddd ;
border-radius : 4 px ;
}
.delete-btn {
background : #ef4444 ;
color : white ;
border : none ;
padding : 0.5 rem 1 rem ;
border-radius : 4 px ;
cursor : pointer ;
}
.delete-btn:hover {
background : #dc2626 ;
}
</ style >
<!-- src/pages/Tasks.svelte -->
< script lang = "ts" >
import { onMount } from 'svelte'
import { taskStore , type TaskFilter } from '../stores/tasks'
import TaskForm from '../components/TaskForm.svelte'
import TaskItem from '../components/TaskItem.svelte'
const filters : TaskFilter [] = [ 'all' , 'pending' , 'in-progress' , 'completed' ]
onMount (() => {
taskStore . fetchTasks ()
})
</ script >
< div class = "tasks-page" >
< h1 > Task Manager </ h1 >
< TaskForm />
< div class = "filters" >
{# each filters as f }
< button
class : active = { $ taskStore . filter === f }
on : click = { () => taskStore . filter . set ( f ) }
>
{ f }
</ button >
{/ each }
</ div >
{# if $ taskStore . loading }
< div class = "loading" > Loading tasks... </ div >
{: else if $ taskStore . filteredTasks . length === 0 }
< div class = "empty" > No tasks found </ div >
{: else }
< div class = "task-list" >
{# each $ taskStore . filteredTasks as task ( task . id )}
< TaskItem { task } />
{/ each }
</ div >
{/ if }
</ div >
< style >
.tasks-page {
max-width : 800 px ;
margin : 0 auto ;
padding : 2 rem ;
}
h1 {
color : #ff3e00 ;
margin-bottom : 2 rem ;
}
.filters {
display : flex ;
gap : 0.5 rem ;
margin-bottom : 2 rem ;
}
.filters button {
padding : 0.5 rem 1 rem ;
border : 1 px solid #ddd ;
background : white ;
border-radius : 4 px ;
cursor : pointer ;
text-transform : capitalize ;
}
.filters button .active {
background : #ff3e00 ;
color : white ;
border-color : #ff3e00 ;
}
.loading , .empty {
text-align : center ;
padding : 2 rem ;
color : #666 ;
}
</ style >
Building for Production
Produces a single binary with embedded frontend.
Run in production:
MIZU_ENV = production ./bin/server
Troubleshooting
HMR Not Working
Problem : Changes don’t hot-reload.
Solution :
Check Vite dev server is running on port 5173
Verify vite.config.ts HMR config:
server : {
hmr : {
clientPort : 3000 // Must match Mizu port
}
}
Reactivity Not Working
Problem : Component doesn’t update when data changes.
Solution : Use assignments, not mutations:
< script >
let items = [ 1 , 2 , 3 ]
// ❌ Doesn't work
items . push ( 4 )
// ✅ Works
items = [ ... items , 4 ]
</ script >
Store Not Updating
Problem : Store value changes but component doesn’t update.
Solution : Make sure you’re using the $ prefix:
< script >
import { count } from './stores/counter'
</ script >
<!-- ❌ Wrong -->
< p > { count } </ p >
<!-- ✅ Correct -->
< p > { $ count } </ p >
TypeScript Errors with Stores
Problem : Type errors when using stores.
Solution : Properly type your stores:
import { writable , type Writable } from 'svelte/store'
interface User {
name : string
email : string
}
export const user : Writable < User > = writable ({ name: '' , email: '' })
Component Props Not Updating
Problem : Props don’t reflect parent changes.
Solution : Make sure you’re not reassigning props directly:
< script >
export let value : number
// ❌ Don't do this
value = 123
// ✅ Use an event to notify parent
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher ()
dispatch ( 'change' , 123 )
</ script >
When to Choose Svelte
Choose Svelte if:
You want the smallest possible bundle size
You value minimal boilerplate and clean syntax
You need excellent performance out of the box
You’re building a new project (not integrating with existing React/Vue)
You want built-in transitions and animations
You prefer compile-time over runtime approaches
Consider alternatives if:
React : You need the largest ecosystem, mature libraries, or are integrating with existing React code
Vue : You want a larger community, more third-party components, or progressive adoption
SvelteKit : You need SSR, static generation, or file-based routing (Svelte’s official framework)
Angular : You need a full enterprise framework with everything included
Next Steps
SvelteKit Full-featured Svelte framework with routing and SSR
Svelte Tutorial Official interactive Svelte tutorial
Svelte Examples Official Svelte code examples
Deployment Build and deploy your app