lunx.docs
DocsFrameworksSvelte

Svelte Adapter

Svelte 5 with Runes, zero-runtime compiled components, native TypeScript, CSS scoping, and transitions — the fastest UI framework paired with the fastest build tool.

Svelte 5 · Runes · Zero Runtime
Svelte 5 Runes

Full Svelte 5 Runes support: $state, $derived, $effect, $props — reactivity without a virtual DOM.

Zero runtime

Svelte compiles to vanilla JS — no framework runtime in the bundle. The smallest possible output.

Scoped CSS

<style> blocks are automatically scoped to the component without any class hashing setup.

Transitions built-in

svelte/transition and svelte/animate provide GPU-accelerated enter/exit animations with zero deps.

TypeScript native

lang='ts' on script blocks. Full type-checking via svelte-check and the official VS Code extension.

Svelte stores

Reactive writable, readable, and derived stores for cross-component state sharing.

Setup

bash
npm create lunx-dev@latest my-svelte-app -- --framework svelte --ts
cd my-svelte-app && npm install && npm run dev

Svelte 5 component with Runes

htmlsrc/lib/Counter.svelte
<script lang="ts">
  // Svelte 5 Runes — replaces let, reactive statements, and stores
  let { initialCount = 0 }: { initialCount?: number } = $props()

  let count    = $state(initialCount)
  let doubled  = $derived(count * 2)
  let history  = $state<number[]>([])

  $effect(() => {
    // Runs when count changes — like useEffect but automatic
    history = [...history, count]
    console.log(`Count changed to ${count}`)
  })

  function increment() { count++ }
  function decrement() { count-- }
  function reset()     { count = initialCount }
</script>

<div class="counter">
  <h2>Count: {count}</h2>
  <p>Doubled: {doubled}</p>
  <div class="controls">
    <button onclick={decrement}>−</button>
    <button onclick={increment}>+</button>
    <button onclick={reset}>Reset</button>
  </div>
  <details>
    <summary>History ({history.length} changes)</summary>
    <ol>{#each history as n}<li>{n}</li>{/each}</ol>
  </details>
</div>

<style>
  .counter  { display: flex; flex-direction: column; gap: 1rem; padding: 1.5rem; }
  .controls { display: flex; gap: 0.5rem; }
  button    { padding: 0.5rem 1rem; border-radius: 8px; cursor: pointer; }
</style>

Routing with svelte-routing

bash
npm install svelte-routing
htmlsrc/App.svelte
<script lang="ts">
  import { Router, Route, Link } from 'svelte-routing'
  import Home    from '@/pages/Home.svelte'
  import About   from '@/pages/About.svelte'
  import Profile from '@/pages/Profile.svelte'
</script>

<Router>
  <nav>
    <Link to="/">Home</Link>
    <Link to="/about">About</Link>
    <Link to="/profile">Profile</Link>
  </nav>

  <main>
    <Route path="/"        component={Home} />
    <Route path="/about"   component={About} />
    <Route path="/profile" component={Profile} />
  </main>
</Router>

Svelte stores for state

typescriptsrc/lib/stores/cart.ts
import { writable, derived, get } from 'svelte/store'

export interface CartItem {
  id:       string
  name:     string
  price:    number
  quantity: number
}

// Writable store — mutable state
export const cartItems = writable<CartItem[]>([])

// Derived store — computed from cartItems
export const cartTotal = derived(
  cartItems,
  $items => $items.reduce((sum, item) => sum + item.price * item.quantity, 0)
)

export const cartCount = derived(
  cartItems,
  $items => $items.reduce((sum, item) => sum + item.quantity, 0)
)

// Actions
export function addToCart(item: Omit<CartItem, 'quantity'>) {
  cartItems.update(items => {
    const existing = items.find(i => i.id === item.id)
    if (existing) {
      return items.map(i => i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i)
    }
    return [...items, { ...item, quantity: 1 }]
  })
}

export function removeFromCart(id: string) {
  cartItems.update(items => items.filter(i => i.id !== id))
}

Svelte transitions

htmlsrc/lib/Toast.svelte
<script lang="ts">
  import { fly, fade } from 'svelte/transition'
  import { flip }      from 'svelte/animate'

  interface Toast { id: number; message: string; type: 'success' | 'error' }
  let { toasts }: { toasts: Toast[] } = $props()
</script>

{#each toasts as toast (toast.id)}
  <div
    class="toast toast--{toast.type}"
    animate:flip={{ duration: 200 }}
    in:fly={{ y: 20, duration: 300 }}
    out:fade={{ duration: 200 }}
  >
    {toast.message}
  </div>
{/each}
Svelte 4 compatibility
Lunx supports both Svelte 4 (reactive declarations with $:) and Svelte 5 (Runes). The adapter auto-detects your installed Svelte version. You can mix Svelte 4 and Svelte 5 components in the same project during migration.