onframe
Rich Harris 8 months ago
commit db90c4ca65

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: reinstate missing prefersReducedMotion export

@ -12,6 +12,7 @@ env:
jobs:
Tests:
permissions: {}
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
@ -41,6 +42,7 @@ jobs:
env:
CI: true
Lint:
permissions: {}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
@ -59,8 +61,9 @@ jobs:
run: pnpm lint
- name: build and check generated types
if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail
run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); }
run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); }
Benchmarks:
permissions: {}
runs-on: ubuntu-latest
timeout-minutes: 15
steps:

@ -1,26 +0,0 @@
# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md
name: Docs preview create request
on:
pull_request_target:
branches:
- main
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.SYNC_REQUEST_TOKEN }}
repository: sveltejs/svelte.dev
event-type: docs-preview-create
client-payload: |-
{
"package": "svelte",
"repo": "${{ github.repository }}",
"owner": "${{ github.event.pull_request.head.repo.owner.login }}",
"branch": "${{ github.event.pull_request.head.ref }}",
"pr": ${{ github.event.pull_request.number }}
}

@ -1,27 +0,0 @@
# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md
name: Docs preview delete request
on:
pull_request_target:
branches:
- main
types: [closed]
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.SYNC_REQUEST_TOKEN }}
repository: sveltejs/svelte.dev
event-type: docs-preview-delete
client-payload: |-
{
"package": "svelte",
"repo": "${{ github.repository }}",
"owner": "${{ github.event.pull_request.head.repo.owner.login }}",
"branch": "${{ github.event.pull_request.head.ref }}",
"pr": ${{ github.event.pull_request.number }}
}

@ -9,6 +9,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'sveltejs/svelte' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run')
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/github-script@v6
with:
script: |

@ -6,11 +6,15 @@ on:
types:
- completed
permissions:
pull-requests: write
jobs:
build:
name: 'Update comment'
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Download artifact
uses: actions/download-artifact@v4
with:

@ -3,6 +3,8 @@ on: [push, pull_request]
jobs:
build:
permissions: {}
runs-on: ubuntu-latest
steps:
@ -12,7 +14,7 @@ jobs:
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 18.x
node-version: 22.x
cache: pnpm
- name: Install dependencies

@ -17,6 +17,7 @@ jobs:
name: Release
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Checkout Repo
uses: actions/checkout@v4
with:
@ -33,7 +34,7 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); }
run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); }
- name: Create Release Pull Request or Publish to npm
id: changesets

@ -1,22 +0,0 @@
# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md
name: Sync request
on:
push:
branches:
- main
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.SYNC_REQUEST_TOKEN }}
repository: sveltejs/svelte.dev
event-type: sync-request
client-payload: |-
{
"package": "svelte"
}

@ -51,7 +51,7 @@ We use [GitHub issues](https://github.com/sveltejs/svelte/issues) for our public
If you have questions about using Svelte, contact us on Discord at [svelte.dev/chat](https://svelte.dev/chat), and we will do our best to answer your questions.
If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml)
If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml).
### Reporting new issues
@ -62,8 +62,6 @@ When [opening a new issue](https://github.com/sveltejs/svelte/issues/new/choose)
## Pull requests
> HEADS UP: Svelte 5 will likely change a lot on the compiler. For that reason, please don't open PRs that are large in scope, touch more than a couple of files etc. In other words, bug fixes are fine, but big feature PRs will likely not be merged.
### Proposing a change
If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with [feature template](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml).

@ -1,4 +1,4 @@
Copyright (c) 2016-24 [these people](https://github.com/sveltejs/svelte/graphs/contributors)
Copyright (c) 2016-2025 [Svelte Contributors](https://github.com/sveltejs/svelte/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

@ -1,6 +1,11 @@
[![Cybernetically enhanced web apps: Svelte](https://sveltejs.github.io/assets/banner.png)](https://svelte.dev)
<a href="https://svelte.dev">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/banner_dark.png">
<img src="assets/banner.png" alt="Svelte - web development for the rest of us" />
</picture>
</a>
[![license](https://img.shields.io/npm/l/svelte.svg)](LICENSE.md) [![Chat](https://img.shields.io/discord/457912077277855764?label=chat&logo=discord)](https://svelte.dev/chat)
[![License](https://img.shields.io/npm/l/svelte.svg)](LICENSE.md) [![Chat](https://img.shields.io/discord/457912077277855764?label=chat&logo=discord)](https://svelte.dev/chat)
## What is Svelte?
@ -24,10 +29,6 @@ You may view [our roadmap](https://svelte.dev/roadmap) if you'd like to see what
Please see the [Contributing Guide](CONTRIBUTING.md) and the [`svelte`](packages/svelte) package for information on contributing to Svelte.
### svelte.dev
The source code for https://svelte.dev lives in the [sites](https://github.com/sveltejs/svelte/tree/master/sites/svelte.dev) folder, with all the documentation right [here](https://github.com/sveltejs/svelte/tree/master/documentation). The site is built with [SvelteKit](https://svelte.dev/docs/kit).
## Is svelte.dev down?
Probably not, but it's possible. If you can't seem to access any `.dev` sites, check out [this SuperUser question and answer](https://superuser.com/q/1413402).

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

@ -20,12 +20,12 @@ function setup() {
return {
destroy,
run() {
$.flush_sync(() => {
$.flush(() => {
$.set(head, 1);
});
assert($.get(computed5) === 6);
for (let i = 0; i < 1000; i++) {
$.flush_sync(() => {
$.flush(() => {
$.set(head, i);
});
assert($.get(computed5) === 6);
@ -45,7 +45,7 @@ export async function kairo_avoidable_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -74,7 +74,7 @@ export async function kairo_avoidable_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -25,12 +25,12 @@ function setup() {
return {
destroy,
run() {
$.flush_sync(() => {
$.flush(() => {
$.set(head, 1);
});
counter = 0;
for (let i = 0; i < 50; i++) {
$.flush_sync(() => {
$.flush(() => {
$.set(head, i);
});
assert($.get(last) === i + 50);
@ -51,7 +51,7 @@ export async function kairo_broad_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -80,7 +80,7 @@ export async function kairo_broad_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -25,12 +25,12 @@ function setup() {
return {
destroy,
run() {
$.flush_sync(() => {
$.flush(() => {
$.set(head, 1);
});
counter = 0;
for (let i = 0; i < iter; i++) {
$.flush_sync(() => {
$.flush(() => {
$.set(head, i);
});
assert($.get(current) === len + i);
@ -51,7 +51,7 @@ export async function kairo_deep_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -80,7 +80,7 @@ export async function kairo_deep_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -28,13 +28,13 @@ function setup() {
return {
destroy,
run() {
$.flush_sync(() => {
$.flush(() => {
$.set(head, 1);
});
assert($.get(sum) === 2 * width);
counter = 0;
for (let i = 0; i < 500; i++) {
$.flush_sync(() => {
$.flush(() => {
$.set(head, i);
});
assert($.get(sum) === (i + 1) * width);
@ -55,7 +55,7 @@ export async function kairo_diamond_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -84,7 +84,7 @@ export async function kairo_diamond_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -22,13 +22,13 @@ function setup() {
destroy,
run() {
for (let i = 0; i < 10; i++) {
$.flush_sync(() => {
$.flush(() => {
$.set(heads[i], i);
});
assert($.get(splited[i]) === i + 1);
}
for (let i = 0; i < 10; i++) {
$.flush_sync(() => {
$.flush(() => {
$.set(heads[i], i * 2);
});
assert($.get(splited[i]) === i * 2 + 1);
@ -48,7 +48,7 @@ export async function kairo_mux_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -77,7 +77,7 @@ export async function kairo_mux_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -25,13 +25,13 @@ function setup() {
return {
destroy,
run() {
$.flush_sync(() => {
$.flush(() => {
$.set(head, 1);
});
assert($.get(current) === size);
counter = 0;
for (let i = 0; i < 100; i++) {
$.flush_sync(() => {
$.flush(() => {
$.set(head, i);
});
assert($.get(current) === i * size);
@ -52,7 +52,7 @@ export async function kairo_repeated_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -81,7 +81,7 @@ export async function kairo_repeated_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -38,13 +38,13 @@ function setup() {
destroy,
run() {
const constant = count(width);
$.flush_sync(() => {
$.flush(() => {
$.set(head, 1);
});
assert($.get(sum) === constant);
counter = 0;
for (let i = 0; i < 100; i++) {
$.flush_sync(() => {
$.flush(() => {
$.set(head, i);
});
assert($.get(sum) === constant - width + i * width);
@ -65,7 +65,7 @@ export async function kairo_triangle_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -94,7 +94,7 @@ export async function kairo_triangle_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -25,13 +25,13 @@ function setup() {
return {
destroy,
run() {
$.flush_sync(() => {
$.flush(() => {
$.set(head, 1);
});
assert($.get(current) === 40);
counter = 0;
for (let i = 0; i < 100; i++) {
$.flush_sync(() => {
$.flush(() => {
$.set(head, i);
});
}
@ -51,7 +51,7 @@ export async function kairo_unstable_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -80,7 +80,7 @@ export async function kairo_unstable_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -51,11 +51,11 @@ function setup() {
*/
run(i) {
res.length = 0;
$.flush_sync(() => {
$.flush(() => {
$.set(B, 1);
$.set(A, 1 + i * 2);
});
$.flush_sync(() => {
$.flush(() => {
$.set(A, 2 + i * 2);
$.set(B, 2);
});

@ -2,7 +2,6 @@ import fs from 'node:fs';
import path from 'node:path';
import { execSync, fork } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { benchmarks } from '../benchmarks.js';
// if (execSync('git status --porcelain').toString().trim()) {
// console.error('Working directory is not clean');

@ -1,7 +1,7 @@
import { benchmarks } from '../benchmarks.js';
import { reactivity_benchmarks } from '../benchmarks/reactivity/index.js';
const results = [];
for (const benchmark of benchmarks) {
for (const benchmark of reactivity_benchmarks) {
const result = await benchmark();
console.error(result.benchmark);
results.push(result);

@ -2,7 +2,7 @@
title: Getting started
---
We recommend using [SvelteKit](../kit), the official application framework from the Svelte team powered by [Vite](https://vite.dev/):
We recommend using [SvelteKit](../kit), which lets you [build almost anything](../kit/project-types). It's the official application framework from the Svelte team and powered by [Vite](https://vite.dev/). Create a new project with:
```bash
npx sv create myapp
@ -15,7 +15,9 @@ Don't worry if you don't know Svelte yet! You can ignore all the nice features S
## Alternatives to SvelteKit
You can also use Svelte directly with Vite by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS and CSS files inside the `dist` directory using [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). In most cases, you will probably need to [choose a routing library](faq#Is-there-a-router) as well.
You can also use Svelte directly with Vite by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS, and CSS files inside the `dist` directory using [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). In most cases, you will probably need to [choose a routing library](faq#Is-there-a-router) as well.
>[!NOTE] Vite is often used in standalone mode to build [single page apps (SPAs)](../kit/glossary#SPA), which you can also [build with SvelteKit](../kit/single-page-apps).
There are also plugins for [Rollup](https://github.com/sveltejs/rollup-plugin-svelte), [Webpack](https://github.com/sveltejs/svelte-loader) [and a few others](https://sveltesociety.dev/packages?category=build-plugins), but we recommend Vite.

@ -4,7 +4,7 @@ title: .svelte.js and .svelte.ts files
Besides `.svelte` files, Svelte also operates on `.svelte.js` and `.svelte.ts` files.
These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app.
These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app (though note that you [cannot export reassigned state]($state#Passing-state-across-modules)).
> [!LEGACY]
> This is a concept that didn't exist prior to Svelte 5

@ -1,139 +0,0 @@
---
title: Public API of a component
---
### Public API of a component
Svelte uses the `$props` rune to declare _properties_ or _props_, which means describing the public interface of the component which becomes accessible to consumers of the component.
> [!NOTE] `$props` is one of several runes, which are special hints for Svelte's compiler to make things reactive.
```svelte
<script>
let { foo, bar, baz } = $props();
// Values that are passed in as props
// are immediately available
console.log({ foo, bar, baz });
</script>
```
You can specify a fallback value for a prop. It will be used if the component's consumer doesn't specify the prop on the component when instantiating the component, or if the passed value is `undefined` at some point.
```svelte
<script>
let { foo = 'optional default initial value' } = $props();
</script>
```
To get all properties, use rest syntax:
```svelte
<script>
let { a, b, c, ...everythingElse } = $props();
</script>
```
You can use reserved words as prop names.
```svelte
<script>
// creates a `class` property, even
// though it is a reserved word
let { class: className } = $props();
</script>
```
If you're using TypeScript, you can declare the prop types:
```svelte
<script lang="ts">
interface Props {
required: string;
optional?: number;
[key: string]: unknown;
}
let { required, optional, ...everythingElse }: Props = $props();
</script>
```
If you're using JavaScript, you can declare the prop types using JSDoc:
```svelte
<script>
/** @type {{ x: string }} */
let { x } = $props();
// or use @typedef if you want to document the properties:
/**
* @typedef {Object} MyProps
* @property {string} y Some documentation
*/
/** @type {MyProps} */
let { y } = $props();
</script>
```
If you export a `const`, `class` or `function`, it is readonly from outside the component.
```svelte
<script>
export const thisIs = 'readonly';
export function greet(name) {
alert(`hello ${name}!`);
}
</script>
```
Readonly props can be accessed as properties on the element, tied to the component using [`bind:this` syntax](bindings#bind:this).
### Reactive variables
To change component state and trigger a re-render, just assign to a locally declared variable that was declared using the `$state` rune.
Update expressions (`count += 1`) and property assignments (`obj.x = y`) have the same effect.
```svelte
<script>
let count = $state(0);
function handleClick() {
// calling this function will trigger an
// update if the markup references `count`
count = count + 1;
}
</script>
```
Svelte's `<script>` blocks are run only when the component is created, so assignments within a `<script>` block are not automatically run again when a prop updates.
```svelte
<script>
let { person } = $props();
// this will only set `name` on component creation
// it will not update when `person` does
let { name } = person;
</script>
```
If you'd like to react to changes to a prop, use the `$derived` or `$effect` runes instead.
```svelte
<script>
let count = $state(0);
let double = $derived(count * 2);
$effect(() => {
if (count > 10) {
alert('Too high!');
}
});
</script>
```
For more information on reactivity, read the documentation around runes.

@ -1,144 +0,0 @@
---
title: Reactivity fundamentals
---
Reactivity is at the heart of interactive UIs. When you click a button, you expect some kind of response. It's your job as a developer to make this happen. It's Svelte's job to make your job as intuitive as possible, by providing a good API to express reactive systems.
## Runes
Svelte 5 uses _runes_, a powerful set of primitives for controlling reactivity inside your Svelte components and inside `.svelte.js` and `.svelte.ts` modules.
Runes are function-like symbols that provide instructions to the Svelte compiler. You don't need to import them from anywhere — when you use Svelte, they're part of the language.
The following sections introduce the most important runes for declare state, derived state and side effects at a high level. For more details refer to the later sections on [state](state) and [side effects](side-effects).
## `$state`
Reactive state is declared with the `$state` rune:
```svelte
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
clicks: {count}
</button>
```
You can also use `$state` in class fields (whether public or private):
```js
// @errors: 7006 2554
class Todo {
done = $state(false);
text = $state();
constructor(text) {
this.text = text;
}
}
```
> [!LEGACY]
> In Svelte 4, state was implicitly reactive if the variable was declared at the top level
>
> ```svelte
> <script>
> let count = 0;
> </script>
>
> <button on:click={() => count++}>
> clicks: {count}
> </button>
> ```
## `$derived`
Derived state is declared with the `$derived` rune:
```svelte
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<button onclick={() => count++}>
{doubled}
</button>
<p>{count} doubled is {doubled}</p>
```
The expression inside `$derived(...)` should be free of side-effects. Svelte will disallow state changes (e.g. `count++`) inside derived expressions.
As with `$state`, you can mark class fields as `$derived`.
> [!LEGACY]
> In Svelte 4, you could use reactive statements for this.
>
> ```svelte
> <script>
> let count = 0;
> $: doubled = count * 2;
> </script>
>
> <button on:click={() => count++}>
> {doubled}
> </button>
>
> <p>{count} doubled is {doubled}</p>
> ```
>
> This only worked at the top level of a component.
## `$effect`
To run _side-effects_ when the component is mounted to the DOM, and when values change, we can use the `$effect` rune ([demo](/playground/untitled#H4sIAAAAAAAAE31T24rbMBD9lUG7kAQ2sbdlX7xOYNk_aB_rQhRpbAsU2UiTW0P-vbrYubSlYGzmzMzROTPymdVKo2PFjzMzfIusYB99z14YnfoQuD1qQh-7bmdFQEonrOppVZmKNBI49QthCc-OOOH0LZ-9jxnR6c7eUpOnuv6KeT5JFdcqbvbcBcgDz1jXKGg6ncFyBedYR6IzLrAZwiN5vtSxaJA-EzadfJEjKw11C6GR22-BLH8B_wxdByWpvUYtqqal2XB6RVkG1CoHB6U1WJzbnYFDiwb3aGEdDa3Bm1oH12sQLTcNPp7r56m_00mHocSG97_zd7ICUXonA5fwKbPbkE2ZtMJGGVkEdctzQi4QzSwr9prnFYNk5hpmqVuqPQjNnfOJoMF22lUsrq_UfIN6lfSVyvQ7grB3X2mjMZYO3XO9w-U5iLx42qg29md3BP_ni5P4gy9ikTBlHxjLzAtPDlyYZmRdjAbGq7HprEQ7p64v4LU_guu0kvAkhBim3nMplWl8FreQD-CW20aZR0wq12t-KqDWeBywhvexKC3memmDwlHAv9q4Vo2ZK8KtK0CgX7u9J8wXbzdKv-nRnfF_2baTqlYoWUF2h5efl9-n0O6koAMAAA==)):
```svelte
<script>
let size = $state(50);
let color = $state('#ff3e00');
let canvas;
$effect(() => {
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
// this will re-run whenever `color` or `size` change
context.fillStyle = color;
context.fillRect(0, 0, size, size);
});
</script>
<canvas bind:this={canvas} width="100" height="100" />
```
The function passed to `$effect` will run when the component mounts, and will re-run after any changes to the values it reads that were declared with `$state` or `$derived` (including those passed in with `$props`). Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied.
> [!LEGACY]
> In Svelte 4, you could use reactive statements for this.
>
> ```svelte
> <script>
> let size = 50;
> let color = '#ff3e00';
>
> let canvas;
>
> $: {
> const context = canvas.getContext('2d');
> context.clearRect(0, 0, canvas.width, canvas.height);
>
> // this will re-run whenever `color` or `size` change
> context.fillStyle = color;
> context.fillRect(0, 0, size, size);
> }
> </script>
>
> <canvas bind:this={canvas} width="100" height="100" />
> ```
>
> This only worked at the top level of a component.

@ -2,7 +2,7 @@
title: What are runes?
---
> [!NOTE] **rune** /ro͞on/ _noun_
> [!NOTE] **rune** /ruːn/ _noun_
>
> A letter or mark used as a mystical or magic symbol.

@ -44,12 +44,7 @@ todos[0].done = !todos[0].done;
If you push a new object to the array, it will also be proxified:
```js
// @filename: ambient.d.ts
declare global {
const todos: Array<{ done: boolean, text: string }>
}
// @filename: index.js
let todos = [{ done: false, text: 'add more todos' }];
// ---cut---
todos.push({
done: false,
@ -255,3 +250,83 @@ console.log(total.value); // 7
```
...though if you find yourself writing code like that, consider using [classes](#Classes) instead.
## Passing state across modules
You can declare state in `.svelte.js` and `.svelte.ts` files, but you can only _export_ that state if it's not directly reassigned. In other words you can't do this:
```js
/// file: state.svelte.js
export let count = $state(0);
export function increment() {
count += 1;
}
```
That's because every reference to `count` is transformed by the Svelte compiler — the code above is roughly equivalent to this:
```js
/// file: state.svelte.js (compiler output)
// @filename: index.ts
interface Signal<T> {
value: T;
}
interface Svelte {
state<T>(value?: T): Signal<T>;
get<T>(source: Signal<T>): T;
set<T>(source: Signal<T>, value: T): void;
}
declare const $: Svelte;
// ---cut---
export let count = $.state(0);
export function increment() {
$.set(count, $.get(count) + 1);
}
```
> [!NOTE] You can see the code Svelte generates by clicking the 'JS Output' tab in the [playground](/playground).
Since the compiler only operates on one file at a time, if another file imports `count` Svelte doesn't know that it needs to wrap each reference in `$.get` and `$.set`:
```js
// @filename: state.svelte.js
export let count = 0;
// @filename: index.js
// ---cut---
import { count } from './state.svelte.js';
console.log(typeof count); // 'object', not 'number'
```
This leaves you with two options for sharing state between modules — either don't reassign it...
```js
// This is allowed — since we're updating
// `counter.count` rather than `counter`,
// Svelte doesn't wrap it in `$.state`
export const counter = $state({
count: 0
});
export function increment() {
counter.count += 1;
}
```
...or don't directly export it:
```js
let count = $state(0);
export function getCount() {
return count;
}
export function increment() {
count += 1;
}
```

@ -51,3 +51,62 @@ In essence, `$derived(expression)` is equivalent to `$derived.by(() => expressio
Anything read synchronously inside the `$derived` expression (or `$derived.by` function body) is considered a _dependency_ of the derived state. When the state changes, the derived will be marked as _dirty_ and recalculated when it is next read.
To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack).
## Overriding derived values
Derived expressions are recalculated when their dependencies change, but you can temporarily override their values by reassigning them (unless they are declared with `const`). This can be useful for things like _optimistic UI_, where a value is derived from the 'source of truth' (such as data from your server) but you'd like to show immediate feedback to the user:
```svelte
<script>
let { post, like } = $props();
let likes = $derived(post.likes);
async function onclick() {
// increment the `likes` count immediately...
likes += 1;
// and tell the server, which will eventually update `post`
try {
await like();
} catch {
// failed! roll back the change
likes -= 1;
}
}
</script>
<button {onclick}>🧡 {likes}</button>
```
> [!NOTE] Prior to Svelte 5.25, deriveds were read-only.
## Deriveds and reactivity
Unlike `$state`, which converts objects and arrays to [deeply reactive proxies]($state#Deep-state), `$derived` values are left as-is. For example, [in a case like this](/playground/untitled#H4sIAAAAAAAAE4VU22rjMBD9lUHd3aaQi9PdstS1A3t5XvpQ2Ic4D7I1iUUV2UjjNMX431eS7TRdSosxgjMzZ45mjt0yzffIYibvy0ojFJWqDKCQVBk2ZVup0LJ43TJ6rn2aBxw-FP2o67k9oCKP5dziW3hRaUJNjoYltjCyplWmM1JIIAn3FlL4ZIkTTtYez6jtj4w8WwyXv9GiIXiQxLVs9pfTMR7EuoSLIuLFbX7Z4930bZo_nBrD1bs834tlfvsBz9_SyX6PZXu9XaL4gOWn4sXjeyzftv4ZWfyxubpzxzg6LfD4MrooxELEosKCUPigQCMPKCZh0OtQE1iSxcsmdHuBvCiHZXALLXiN08EL3RRkaJ_kDVGle0HcSD5TPEeVtj67O4Nrg9aiSNtBY5oODJkrL5QsHtN2cgXp6nSJMWzpWWGasdlsGEMbzi5jPr5KFr0Ep7pdeM2-TCelCddIhDxAobi1jqF3cMaC1RKp64bAW9iFAmXGIHfd4wNXDabtOLN53w8W53VvJoZLh7xk4Rr3CoL-UNoLhWHrT1JQGcM17u96oES5K-kc2XOzkzqGCKL5De79OUTyyrg1zgwXsrEx3ESfx4Bz0M5UjVMHB24mw9SuXtXFoN13fYKOM1tyUT3FbvbWmSWCZX2Er-41u5xPoml45svRahl9Wb9aasbINJixDZwcPTbyTLZSUsAvrg_cPuCR7s782_WU8343Y72Qtlb8OYatwuOQvuN13M_hJKNfxann1v1U_B1KZ_D_mzhzhz24fw85CSz2irtN9w9HshBK7AQAAA==)...
```svelte
let items = $state([...]);
let index = $state(0);
let selected = $derived(items[index]);
```
...you can change (or `bind:` to) properties of `selected` and it will affect the underlying `items` array. If `items` was _not_ deeply reactive, mutating `selected` would have no effect.
## Update propagation
Svelte uses something called _push-pull reactivity_ — when state is updated, everything that depends on the state (whether directly or indirectly) is immediately notified of the change (the 'push'), but derived values are not re-evaluated until they are actually read (the 'pull').
If the new value of a derived is referentially identical to its previous value, downstream updates will be skipped. In other words, Svelte will only update the text inside the button when `large` changes, not when `count` changes, even though `large` depends on `count`:
```svelte
<script>
let count = $state(0);
let large = $derived(count > 10);
</script>
<button onclick={() => count++}>
{large}
</button>
```

@ -2,15 +2,11 @@
title: $effect
---
Effects are what make your application _do things_. When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes.
Effects are functions that run when state updates, and can be used for things like calling third-party libraries, drawing on `<canvas>` elements, or making network requests. They only run in the browser, not during server-side rendering.
Most of the effects in a Svelte app are created by Svelte itself — they're the bits that update the text in `<h1>hello {name}!</h1>` when `name` changes, for example.
Generally speaking, you should _not_ update state inside effects, as it will make code more convoluted and will often lead to never-ending update cycles. If you find yourself doing so, see [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches.
But you can also create your own effects with the `$effect` rune, which is useful when you need to synchronize an external system (whether that's a library, or a `<canvas>` element, or something across a network) with state inside your Svelte app.
> [!NOTE] Avoid overusing `$effect`! When you do too much work in effects, code often becomes difficult to understand and maintain. See [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches.
Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)):
You can create an effect with the `$effect` rune ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)):
```svelte
<script>
@ -29,14 +25,22 @@ Your effects run after the component has been mounted to the DOM, and in a [micr
});
</script>
<canvas bind:this={canvas} width="100" height="100" />
<canvas bind:this={canvas} width="100" height="100"></canvas>
```
Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied.
When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes.
> [!NOTE] If you're having difficulty understanding why your `$effect` is rerunning or is not running see [understanding dependencies](#Understanding-dependencies). Effects are triggered differently than the `$:` blocks you may be used to if coming from Svelte 4.
### Understanding lifecycle
You can place `$effect` anywhere, not just at the top level of a component, as long as it is called during component initialization (or while a parent effect is active). It is then tied to the lifecycle of the component (or parent effect) and will therefore destroy itself when the component unmounts (or the parent effect is destroyed).
Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes. Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied.
You can return a function from `$effect`, which will run immediately before the effect re-runs, and before it is destroyed ([demo](/playground/untitled#H4sIAAAAAAAAE42RQY-bMBCF_8rI2kPopiXpMQtIPfbeW6m0xjyKtWaM7CFphPjvFVB2k2oPe7LmzXzyezOjaqxDVKefo5JrD3VaBLVXrLu5-tb3X-IZTmat0hHv6cazgCWqk8qiCbaXouRSHISMH1gop4coWrA7JE9bp7PO2QjjuY5vA8fDYZ3hUh7QNDCy2yWUFzTOUilpSj9aG-linaMKFGACtKCmSwvGGYGeLQvCWbtnMq3m34grajxHoa1JOUXI93_V_Sfz7Oz7Mafj0ypN-zvHm8dSAmQITP_xaUq2IU1GO1dp80I2Uh_82dao92Rl9R8GvgF0QrbrUFstcFeq0PgAkha0LoICPoeB4w1SJUvsZcj4rvcMlvmvGlGCv6J-DeSgw2vabQnJlm55p7nM0rcTctYei3HZxZSl7XHVqkHEM3k2zpqXfFyj393zU05fpyI6f0HI0hUoPoamC9roKDeo2ivBH1EnCQOmX9NfYw2GHrgCAAA=)).
You can use `$effect` anywhere, not just at the top level of a component, as long as it is called while a parent effect is running.
> [!NOTE] Svelte uses effects internally to represent logic and expressions in your template — this is how `<h1>hello {name}!</h1>` updates when `name` changes.
An effect can return a _teardown function_ which will run immediately before the effect re-runs ([demo](/playground/untitled#H4sIAAAAAAAAE42SQVODMBCF_8pOxkPRKq3HCsx49K4n64xpskjGkDDJ0tph-O8uINo6HjxB3u7HvrehE07WKDbiyZEhi1osRWksRrF57gQdm6E2CKx_dd43zU3co6VB28mIf-nKO0JH_BmRRRVMQ8XWbXkAgfKtI8jhIpIkXKySu7lSG2tNRGZ1_GlYr1ZTD3ddYFmiosUigbyAbpC2lKbwWJkIB8ZhhxBQBWRSw6FCh3sM8GrYTthL-wqqku4N44TyqEgwF3lmRHr4Op0PGXoH31c5rO8mqV-eOZ49bikgtcHBL55tmhIkEMqg_cFB2TpFxjtg703we6NRL8HQFCS07oSUCZi6Rm04lz1yytIHBKoQpo1w6Gsm4gmyS8b8Y5PydeMdX8gwS2Ok4I-ov5NZtvQde95GMsccn_1wzNKfu3RZtS66cSl9lvL7qO1aIk7knbJGvefdtIOzi73M4bYvovUHDFk6AcX_0HRESxnpBOW_jfCDxIZCi_1L_wm4xGQ60wIAAA==)).
```svelte
<script>
@ -50,7 +54,7 @@ You can return a function from `$effect`, which will run immediately before the
}, milliseconds);
return () => {
// if a callback is provided, it will run
// if a teardown function is provided, it will run
// a) immediately before the effect re-runs
// b) when the component is destroyed
clearInterval(interval);
@ -64,9 +68,13 @@ You can return a function from `$effect`, which will run immediately before the
<button onclick={() => (milliseconds /= 2)}>faster</button>
```
Teardown functions also run when the effect is destroyed, which happens when its parent is destroyed (for example, a component is unmounted) or the parent effect re-runs.
### Understanding dependencies
`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body and registers them as dependencies. When those dependencies change, the `$effect` schedules a rerun.
`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body (including indirectly, via function calls) and registers them as dependencies. When those dependencies change, the `$effect` schedules a re-run.
If `$state` and `$derived` are used directly inside the `$effect` (for example, during creation of a [reactive class](https://svelte.dev/docs/svelte/$state#Classes)), those values will _not_ be treated as dependencies.
Values that are read _asynchronously_ — after an `await` or inside a `setTimeout`, for example — will not be tracked. Here, the canvas will be repainted when `color` changes, but not when `size` changes ([demo](/playground/untitled#H4sIAAAAAAAAE31T246bMBD9lZF3pWSlBEirfaEQqdo_2PatVIpjBrDkGGQPJGnEv1e2IZfVal-wfHzmzJyZ4cIqqdCy9M-F0blDlnqArZjmB3f72XWRHVCRw_bc4me4aDWhJstSlllhZEfbQhekkMDKfwg5PFvihMvX5OXH_CJa1Zrb0-Kpqr5jkiwC48rieuDWQbqgZ6wqFLRcvkC-hYvnkWi1dWqa8ESQTxFRjfQWsOXiWzmr0sSLhEJu3p1YsoJkNUcdZUnN9dagrBu6FVRQHAM10sJRKgUG16bXcGxQ44AGdt7SDkTDdY02iqLHnJVU6hedlWuIp94JW6Tf8oBt_8GdTxlF0b4n0C35ZLBzXb3mmYn3ae6cOW74zj0YVzDNYXRHFt9mprNgHfZSl6mzml8CMoLvTV6wTZIUDEJv5us2iwMtiJRyAKG4tXnhl8O0yhbML0Wm-B7VNlSSSd31BG7z8oIZZ6dgIffAVY_5xdU9Qrz1Bnx8fCfwtZ7v8Qc9j3nB8PqgmMWlHIID6-bkVaPZwDySfWtKNGtquxQ23Qlsq2QJT0KIqb8dL0up6xQ2eIBkAg_c1FI_YqW0neLnFCqFpwmreedJYT7XX8FVOBfwWRhXstZrSXiwKQjUhOZeMIleb5JZfHWn2Yq5pWEpmR7Hv-N_wEqT8hEEAAA=)):
@ -125,17 +133,35 @@ An effect only reruns when the object it reads changes, not when a property insi
<p>{state.value} doubled is {derived.value}</p>
```
An effect only depends on the values that it read the last time it ran. If `a` is true, changes to `b` will [not cause this effect to rerun](/playground/untitled#H4sIAAAAAAAAE3WQ0WrDMAxFf0U1hTow1vcsMfQ7lj3YjlxEXTvEymC4_vfFC6Ewtidxde8RkrJw5DGJ9j2LoO8oWnGZJvEi-GuqIn2iZ1x1istsa6dLdqaJ1RAG9sigoYdjYs0onfYJm7fdMX85q3dE59CylA30CnJtDWxjSNHjq49XeZqXEChcT9usLUAOpIbHA0yzM78oColGhDVofLS3neZSS6mqOz-XD51ZmGOAGKwne-vztk-956CL0kAJsi7decupf4l658EUZX4I8yTWt93jSI5wFC3PC5aP8g0Aje5DcQEAAA==):
An effect only depends on the values that it read the last time it ran. This has interesting implications for effects that have conditional code.
For instance, if `condition` is `true` in the code snippet below, the code inside the `if` block will run and `color` will be evaluated. As such, changes to either `condition` or `color` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE21RQW6DMBD8ytaNBJHaJFLViwNIVZ8RcnBgXVk1xsILTYT4e20TQg89IOPZ2fHM7siMaJBx9tmaWpFqjQNlAKXEihx7YVJpdIyfRkY3G4gB8Pi97cPanRtQU8AuwuF_eNUaQuPlOMtc1SlLRWlKUo1tOwJflUikQHZtA0klzCDc64Imx0ANn8bInV1CDhtHgjClrsftcSXotluLybOUb3g4JJHhOZs5WZpuIS9gjNqkJKQP5e2ClrR4SMdZ13E4xZ8zTPOTJU2A2uE_PQ9COCI926_hTVarIU4hu_REPlBrKq2q73ycrf1N-vS4TMUsulaVg3EtR8H9rFgsg8uUsT1B2F9eshigZHBRpuaD0D3mY8Qm2BfB5N2YyRzdNEYVDy0Ja-WsFjcOUuP1HvFLWA6H3XuHTUSmmDV2--0TXonxsKbp7G9C6R__NONS-MFNvxj_d6mBAgAA).
Conversely, if `condition` is `false`, `color` will not be evaluated, and the effect will _only_ re-run again when `condition` changes.
```ts
let a = false;
let b = false;
// @filename: ambient.d.ts
declare module 'canvas-confetti' {
interface ConfettiOptions {
colors: string[];
}
function confetti(opts?: ConfettiOptions): void;
export default confetti;
}
// @filename: index.js
// ---cut---
$effect(() => {
console.log('running');
import confetti from 'canvas-confetti';
let condition = $state(true);
let color = $state('#ff3e00');
if (a || b) {
console.log('inside if block');
$effect(() => {
if (condition) {
confetti({ colors: [color] });
} else {
confetti();
}
});
```
@ -193,72 +219,25 @@ The `$effect.tracking` rune is an advanced feature that tells you whether or not
<p>in template: {$effect.tracking()}</p> <!-- true -->
```
This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects. Here's a `readable` function that listens to changes from a callback function as long as it's inside a tracking context:
```ts
import { tick } from 'svelte';
export default function readable<T>(
initial_value: T,
start: (callback: (update: (v: T) => T) => T) => () => void
) {
let value = $state(initial_value);
let subscribers = 0;
let stop: null | (() => void) = null;
return {
get value() {
// If in a tracking context ...
if ($effect.tracking()) {
$effect(() => {
// ...and there's no subscribers yet...
if (subscribers === 0) {
// ...invoke the function and listen to changes to update state
stop = start((fn) => (value = fn(value)));
}
subscribers++;
// The return callback is called once a listener unlistens
return () => {
tick().then(() => {
subscribers--;
// If it was the last subscriber...
if (subscribers === 0) {
// ...stop listening to changes
stop?.();
stop = null;
}
});
};
});
}
return value;
}
};
}
```
It is used to implement abstractions like [`createSubscriber`](/docs/svelte/svelte-reactivity#createSubscriber), which will create listeners to update reactive values but _only_ if those values are being tracked (rather than, for example, read inside an event handler).
## `$effect.root`
The `$effect.root` rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for nested effects that you want to manually control. This rune also allows for the creation of effects outside of the component initialisation phase.
```svelte
<script>
let count = $state(0);
```js
const destroy = $effect.root(() => {
$effect(() => {
// setup
});
const cleanup = $effect.root(() => {
$effect(() => {
console.log(count);
});
return () => {
// cleanup
};
});
return () => {
console.log('effect root cleanup');
};
});
</script>
// later...
destroy();
```
## When not to use `$effect`
@ -288,6 +267,8 @@ In general, `$effect` is best considered something of an escape hatch — useful
> [!NOTE] For things that are more complicated than a simple expression like `count * 2`, you can also use `$derived.by`.
If you're using an effect because you want to be able to reassign the derived value (to build an optimistic UI, for example) note that [deriveds can be directly overridden]($derived#Overriding-derived-values) as of Svelte 5.25.
You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/playground/untitled#H4sIAAAAAAAACpVRy26DMBD8FcvKgUhtoIdeHBwp31F6MGSJkBbHwksEQvx77aWQqooq9bgzOzP7mGTdIHipPiZJowOpGJAv0po2VmfnDv4OSBErjYdneHWzBJaCjcx91TWOToUtCIEE3cig0OIty44r5l1oDtjOkyFIsv3GINQ_CNYyGegd1DVUlCR7oU9iilDUcP8S8roYs9n8p2wdYNVFm4csTx872BxNCcjr5I11fdgonEkXsjP2CoUUZWMv6m6wBz2x7yxaM-iJvWeRsvSbSVeUy5i0uf8vKA78NIeJLSZWv1I8jQjLdyK4XuTSeIdmVKJGGI4LdjVOiezwDu1yG74My8PLCQaSiroe5s_5C2PHrkVGAgAA)):
```svelte
@ -316,7 +297,7 @@ You might be tempted to do something convoluted with effects to link one value t
</label>
```
Instead, use callbacks where possible ([demo](/playground/untitled#H4sIAAAAAAAACo1SMW6EMBD8imWluFMSIEUaDiKlvy5lSOHjlhOSMRZeTiDkv8deMEEJRcqdmZ1ZjzzxqpZgePo5cRw18JQA_sSVaPz0rnVk7iDRYxdhYA8vW4Wg0NnwzJRdrfGtUAVKQIYtCsly9pIkp4AZ7cQOezAoEA7JcWUkVBuCdol0dNWrEutWsV5fHfnhPQ5wZJMnCwyejxCh6G6A0V3IHk4zu_jOxzzPBxBld83PTr7xXrb3rUNw8PbiYJ3FP22oTIoLSComq5XuXTeu8LzgnVA3KDgj13wiQ8taRaJ82rzXskYM-URRlsXktejjgNLoo9e4fyf70_8EnwncySX1GuunX6kGRwnzR_BgaPNaGy3FmLJKwrCUeBM6ZUn0Cs2mOlp3vwthQJ5i14P9st9vZqQlsQIAAA==)):
Instead, use `oninput` callbacks or — better still — [function bindings](bind#Function-bindings) where possible ([demo](/playground/untitled#H4sIAAAAAAAAE51SsW6DMBT8FcvqABINdOhCIFKXTt06lg4GHpElYyz8iECIf69tcIIipo6-u3f3fPZMJWuBpvRzkBXyTpKSy5rLq6YRbbgATdOfmeKkrMgCBt9GPpQ66RsItFjJNBzhVScRJBobmumq5wovhSxQABLskAmSk7ckOXtMKyM22ItGhhAk4Z0R0OwIN-tIQzd-90HVhvy2HsGNiQFCMltBgd7XoecV2xzXNV7XaEcth7ZfRv7kujnsTX2Qd7USb5rFjwZkJlgJwpWRcakG04cpOS9oz-QVCuoeInXW-RyEJL-sG0b7Wy6kZWM-u7CFxM5tdrIl9qg72vB74H-y7T2iXROHyVb0CLanp1yNk4D1A1jQ91hzrQSbUtIIGLcir0ylJDm9Q7urz42bX4UwIk2xH2D5Xf4A7SeMcMQCAAA=)):
```svelte
<script>
@ -324,54 +305,26 @@ Instead, use callbacks where possible ([demo](/playground/untitled#H4sIAAAAAAAAC
let spent = $state(0);
let left = $state(total);
function updateSpent(e) {
spent = +e.target.value;
function updateSpent(value) {
spent = value;
left = total - spent;
}
function updateLeft(e) {
left = +e.target.value;
function updateLeft(value) {
left = value;
spent = total - left;
}
</script>
<label>
<input type="range" value={spent} oninput={updateSpent} max={total} />
<input type="range" bind:value={() => spent, updateSpent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" value={left} oninput={updateLeft} max={total} />
<input type="range" bind:value={() => left, updateLeft} max={total} />
{left}/{total} left
</label>
```
If you need to use bindings, for whatever reason (for example when you want some kind of "writable `$derived`"), consider using getters and setters to synchronise state ([demo](/playground/untitled#H4sIAAAAAAAACpWRwW6DMBBEf8WyekikFOihFwcq9TvqHkyyQUjGsfCCQMj_XnvBNKpy6Qn2DTOD1wu_tRocF18Lx9kCFwT4iRvVxenT2syNoDGyWjl4xi93g2AwxPDSXfrW4oc0EjUgwzsqzSr2VhTnxJwNHwf24lAhHIpjVDZNwy1KS5wlNoGMSg9wOCYksQccerMlv65p51X0p_Xpdt_4YEy9yTkmV3z4MJT579-bUqsaNB2kbI0dwlnCgirJe2UakJzVrbkKaqkWivasU1O1ULxnOVk3JU-Uxti0p_-vKO4no_enbQ_yXhnZn0aHs4b1jiJMK7q2zmo1C3bTMG3LaZQVrMjeoSPgaUtkDxePMCEX2Ie6b_8D4WyJJEwCAAA=)):
```svelte
<script>
let total = 100;
let spent = $state(0);
let left = {
get value() {
return total - spent;
},
set value(v) {
spent = total - v;
}
};
</script>
<label>
<input type="range" bind:value={spent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" bind:value={left.value} max={total} />
{left.value}/{total} left
</label>
```
If you absolutely have to update `$state` within an effect and run into an infinite loop because you read and write to the same `$state`, use [untrack](svelte#untrack).

@ -196,4 +196,27 @@ You can, of course, separate the type declaration from the annotation:
</script>
```
> [!NOTE] Interfaces for native DOM elements are provided in the `svelte/elements` module (see [Typing wrapper components](typescript#Typing-wrapper-components))
Adding types is recommended, as it ensures that people using your component can easily discover which props they should provide.
## `$props.id()`
This rune, added in version 5.20.0, generates an ID that is unique to the current component instance. When hydrating a server-rendered component, the value will be consistent between server and client.
This is useful for linking elements via attributes like `for` and `aria-labelledby`.
```svelte
<script>
const uid = $props.id();
</script>
<form>
<label for="{uid}-firstname">First Name: </label>
<input id="{uid}-firstname" type="text" />
<label for="{uid}-lastname">Last Name: </label>
<input id="{uid}-lastname" type="text" />
</form>
```

@ -33,7 +33,7 @@ Now, a component that uses `<FancyInput>` can add the [`bind:`](bind) directive
<!-- prettier-ignore -->
```svelte
/// App.svelte
/// file: App.svelte
<script>
import FancyInput from './FancyInput.svelte';

@ -42,3 +42,20 @@ A convenient way to find the origin of some change is to pass `console.trace` to
// @errors: 2304
$inspect(stuff).with(console.trace);
```
## $inspect.trace(...)
This rune, added in 5.14, causes the surrounding function to be _traced_ in development. Any time the function re-runs as part of an [effect]($effect) or a [derived]($derived), information will be printed to the console about which pieces of reactive state caused the effect to fire.
```svelte
<script>
import { doSomeWork } from './elsewhere';
$effect(() => {
+++$inspect.trace();+++
doSomeWork();
});
</script>
```
`$inspect.trace` takes an optional first argument which will be used as the label.

@ -2,7 +2,7 @@
title: $host
---
When compiling a component as a custom element, the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)):
When compiling a component as a [custom element](custom-elements), the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)):
<!-- prettier-ignore -->
```svelte

@ -185,7 +185,7 @@ You can use HTML comments inside components.
Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually, these are accessibility warnings; make sure that you're disabling them for a good reason.
```svelte
<!-- svelte-ignore a11y-autofocus -->
<!-- svelte-ignore a11y_autofocus -->
<input bind:value={name} autofocus />
```

@ -23,8 +23,6 @@ Iterating over values can be done with an each block. The values in question can
</ul>
```
You can use each blocks to iterate over any array or array-like value — that is, any object with a `length` property.
An each block can also specify an _index_, equivalent to the second argument in an `array.map(...)` callback:
```svelte

@ -112,7 +112,7 @@ Snippets can reference themselves and each other ([demo](/playground/untitled#H4
## Passing snippets to components
Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/playground/untitled#H4sIAAAAAAAAE41SwY6bMBD9lRGplKQlYRMpF5ZF7T_0ttmDwSZYJbZrT9pGlv-9g4Fkk-xhxYV5vHlvhjc-aWQnXJK_-kSxo0jy5IcxSZrg2fSF-yM6FFQ7fbJ1jxSuttJguVd7lEejLcJPVnUCGquPMF9nsVoPjfNnohGx1sohMU4SHbzAa4_t0UNvmcOcGUNDzFP4jeccdikYK2v6sIWQ3lErpui5cDdPF_LmkVy3wlp5Vd5e2U_rHYSe_kYjFtl1KeVnTkljBEIrGBd2sYy8AtsyLlBk9DYhJHtTR_UbBDWybkR8NkqHWyOr_y74ZMNLz9f9AoG6ePkOJLMHLBp-xISvcPf11r0YUuMM2Ysfkgngh5XphUYKkJWU_FFz2UjBkxztSYT0cihR4LOn0tGaPrql439N-7Uh0Dl8MVYbt1jeJ1Fg7xDb_Uw2Y18YQqZ_S2U5FH1pS__dCkWMa3C0uR0pfQRTg89kE4bLLLDS_Dxy_Eywuo1TAnPAw4fqY1rvtH3W9w35ZZMgvU3jq8LhedwkguCHRhT_cMU6eVA5dKLB5wGutCWjlTOslupAxxrxceKoD2hzhe2qbmXHF1v1bbOcNCtW_zpYfVI8h5kQ4qY3mueHTlesW2C7TOEO4hcdwzgf3Nc7cZxUKKC4yuNhvIX_MlV_Xk0EAAA=)):
Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/playground/untitled#H4sIAAAAAAAAE3VS247aMBD9lZGpBGwDASRegonaPvQL2qdlH5zYEKvBNvbQLbL875VzAcKyj3PmzJnLGU8UOwqSkd8KJdaCk4TsZS0cyV49wYuJuQiQpGd-N2bu_ooaI1YwJ57hpVYoFDqSEepKKw3mO7VDeTTaIvxiRS1gb_URxvO0ibrS8WanIrHUyiHs7Vmigy28RmyHHmKvDMbMmFq4cQInvGSwTsBYWYoMVhCSB2rBFFPsyl0uruTlR3JZCWvlTXl1Yy_mawiR_rbZKZrellJ-5JQ0RiBUgnFhJ9OGR7HKmwVoilXeIye8DOJGfYCgRlZ3iE876TBsZPX7hPdteO75PC4QaIo8vwNPePmANQ2fMeEFHrLD7rR1jTNkW986E8C3KwfwVr8HSHOSEBT_kGRozyIkn_zQveXDL3rIfPJHtUDwzShJd_Qk3gQCbOGLsdq4yfTRJopRuin3I7nv6kL7ARRjmLdBDG3uv1mhuLA3V2mKtqNEf_oCn8p9aN-WYqH5peP4kWBl1UwJzAEPT9U7K--0fRrrWnPTXpCm1_EVdXjpNmlA8G1hPPyM1fKgMqjFHjctXGjLhZ05w0qpDhksGrybuNEHtJnCalZWsuaTlfq6nPaaBSv_HKw-K57BjzOiVj9ZKQYKzQjZodYFqydYTRN4gPhVzTDO2xnma3HsVWjaLjT8nbfwHy7Q5f2dBAAA)):
```svelte
<script>
@ -144,7 +144,7 @@ Within the template, snippets are values just like any other. As such, they can
Think about it like passing content instead of data to a component. The concept is similar to slots in web components.
As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component ([demo](/playground/untitled#H4sIAAAAAAAAE41Sy27bMBD8lYVcwHYrW4kBXxRFaP-htzgHSqQsojLJkuu2BqF_74qUrfhxCHQRh7MzO9z1SSM74ZL8zSeKHUSSJz-MSdIET2Y4uD-iQ0Fnp4-2HpDC1VYaLHdqh_JgtEX4yapOQGP1AebrLJzWsXD-QjQi1lo5JMZRooNXeBuwHXoYLHOYM2OoiXkKv_GUwzYFY2VNFxvo0xtqxRR9F-7z04X8fE-uW2GtnJQ3E_tpvYV-oL9Ti0U2hVJFjMMZslcfW-5DWj9zShojEFrBuLCLZR_9CmzLQCwy-psw8rxBgvkNhhpZd8F8NppE7Stbq_8u-GTKS8_XQ9Keqnl5BZP1AzTYP2bDV7i7_9hLEeda0iocNJeNFDzJ0R5Fn142JzA-uzsdBfLhldPxPdMhIPS0H1-M1cYtlnejwdBDfBXZjHXTFOg4BhuOtvTfrVDEmAZG2ew5ezYV-Ew2fVzVAivNTyPHzwSr29AlMAe8f6g-zuWDts-GusAmdBSkv3P7qnB4GpMEEHwsRPEPV6yTe5VDJxp8iXClLRmtnGG1VHva3oCPHQd9QJsrbFd1Kzu-2Khvz8uzZsXqX3urj4rnMBNCXNUG83zf6Yp1C2yXKdxA_KJjGOfRfb0Vh7MKDShEuV-M9_4_nq6svF4EAAA=)):
As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component ([demo](/playground/untitled#H4sIAAAAAAAAE3VSTa_aMBD8Kyu_SkAbCA-JSzBR20N_QXt6vIMTO8SqsY29tI2s_PcqTiB8vaPHs7MzuxuIZgdBMvJLo0QlOElIJZXwJHsLBBvb_XUASc7Mb9Yu_B-hsMMK5sUzvDQahUZPMkJ96aTFfKd3KA_WOISfrFACKmcOMFmk8TWUTjY73RFLoz1C5U4SPWzhrcN2GKDrlcGEWauEnyRwxCaDdQLWyVJksII2uaMWTDPNLtzX5YX8-kgua-GcHJVXI3u5WEPb0d83O03TMZSmfRzOkG1Db7mNacOL19JagVALxoWbztq-H8U6j0SaYp2P2BGbOyQ2v8PQIFMXLKRDk177pq0zf6d8bMrzwBdd0pamyPMb-IjNEzS2f86Gz_Dwf-2F9nvNSUJQ_EOSoTuJNvngqK5v4Pas7n4-OCwlEEJcQTIMO-nSQwtb-GSdsX46e9gbRoP9yGQ11I0rEuycunu6PHx1QnPhxm3SFN15MOlYEFJZtf0dUywMbwZOeBGsrKNLYB54-1R9WNqVdki7usim6VmQphf7mnpshiQRhNAXdoOfMyX3OgMlKtz0cGEcF27uLSul3mewjPjgOOoDukxjPS9rqfh0pb-8zs6aBSt_7505aZ7B9xOi0T9YKW4UooVsr0zB1BTrWQJ3EL-oWcZ572GxFoezCk37QLe3897-B2i2U62uBAAA)):
```svelte
<!-- this is semantically the same as the above -->
@ -165,7 +165,7 @@ As an authoring convenience, snippets declared directly _inside_ a component imp
</Table>
```
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet ([demo](/playground/untitled#H4sIAAAAAAAAE41S247aMBD9lVFYCegGsiDxks1G7T_0bdkHJ3aI1cR27aEtsvzvtZ0LZeGhiiJ5js-cmTMemzS8YybJ320iSM-SPPmmVJImeFEhML9Yh8zHRp51HZDC1JorLI_iiLxXUiN8J1XHoNGyh-U2i9F2SFy-epon1lIY9IwzRwNv8B6wI1oIJXNYEqV8E8sUfuIlh0MKSvPaX-zBpZ-oFRH-m7m7l5m8uyfXLdOaX5X3V_bL9gAu0D98i0V2NSWKwQ4lSN7s0LKLbgtsyxgXmT9NiBe-iaP-DYISSTcj4bcLI7hSDEHL3yu6dkPfBdLS0m1o3nk-LW9gX-gBGss9ZsMXuLu32VjZBdfRaelft5eUN5zRJEd9Zi6dlyEy_ncdOm_IxsGlULe8o5qJNFgE5x_9SWmpzGp9N2-MXQxz4c2cOQ-lZWQyF0Jd2q_-mjI9U1fr4FBPE8iuKTbjjRt2sMBK0svIsQtG6jb2CsQAdQ_1x9f5R9tmIS-yPToK-tNkQRQGL6ObCIIdEpH9wQ3p-Enk0LEGXwe4ktoX2hhFai5Ofi0jPnYc9QF1LrDdRK-rvXjerSfNitQ_TlqeBc1hwRi7yY3F81MnK9KtsF2n8Amis44ilA7VtwfWTyr-kaKV-_X4cH8BTOhfRzcEAAA=)):
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet ([demo](/playground/untitled#H4sIAAAAAAAAE3WOQQrCMBBFrzIMggql3ddY1Du4si5sOmIwnYRkFKX07lKqglqX8_7_w2uRDw1hjlsWI5ZqTPBoLEXMdy3K3fdZDzB5Ndfep_FKVnpWHSKNce1YiCVijirqYLwUJQOYxrsgsLmIOIZjcA1M02w4n-PpomSVvTclqyEutDX6DA2pZ7_ABIVugrmEC3XJH92P55_G39GodCmWBFrQJ2PrQAwdLGHig_NxNv9xrQa1dhWIawrv1Wzeqawa8953D-8QOmaEAQAA)):
```svelte
<!--- file: App.svelte --->

@ -11,4 +11,4 @@ The `{@const ...}` tag defines a local constant.
{/each}
```
`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — or a `<Component />`.
`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a `<Component />` or a `<svelte:boundary>`.

@ -12,10 +12,34 @@ The general syntax is `bind:property={expression}`, where `expression` is an _lv
<input bind:value />
```
Svelte creates an event listener that updates the bound value. If an element already has a listener for the same event, that listener will be fired before the bound value is updated.
Most bindings are _two-way_, meaning that changes to the value will affect the element and vice versa. A few bindings are _readonly_, meaning that changing their value will have no effect on the element.
## Function bindings
You can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation:
```svelte
<input bind:value={
() => value,
(v) => value = v.toLowerCase()}
/>
```
In the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`:
```svelte
<div
bind:clientWidth={null, redraw}
bind:clientHeight={null, redraw}
>...</div>
```
> [!NOTE]
> Function bindings are available in Svelte 5.9.0 and newer.
## `<input bind:value>`
A `bind:value` directive on an `<input>` element binds the input's `value` property:
@ -195,11 +219,10 @@ You can give the `<select>` a default value by adding a `selected` attribute to
- [`volume`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/volume)
- [`muted`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/muted)
...and seven readonly ones:
...and six readonly ones:
- [`duration`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/duration)
- [`buffered`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered)
- [`paused`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/paused)
- [`seekable`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seekable)
- [`seeking`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seeking_event)
- [`ended`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ended)
@ -211,7 +234,7 @@ You can give the `<select>` a default value by adding a `selected` attribute to
## `<video>`
`<video>` elements have all the same bindings as [#audio] elements, plus readonly [`videoWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoWidth) and [`videoHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoHeight) bindings.
`<video>` elements have all the same bindings as [`<audio>`](#audio) elements, plus readonly [`videoWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoWidth) and [`videoHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoHeight) bindings.
## `<img>`
@ -244,7 +267,7 @@ Elements with the `contenteditable` attribute support the following bindings:
<!-- for some reason puts the comment and html on same line -->
<!-- prettier-ignore -->
```svelte
<div contenteditable="true" bind:innerHTML={html} />
<div contenteditable="true" bind:innerHTML={html}></div>
```
## Dimensions
@ -284,7 +307,7 @@ To get a reference to a DOM node, use `bind:this`. The value will be `undefined`
});
</script>
<canvas bind:this={canvas} />
<canvas bind:this={canvas}></canvas>
```
Components also support `bind:this`, allowing you to interact with component instances programmatically.

@ -50,17 +50,16 @@ The `Action` interface receives three optional type arguments — a node type (w
```svelte
<!--- file: App.svelte --->
<script>
import { on } from 'svelte/events';
/**
* @type {import('svelte/action').Action<
* HTMLDivElement,
* null,
* undefined,
* {
* onswiperight: (e: CustomEvent) => void;
* onswipeleft: (e: CustomEvent) => void;
* // ...
* }>}
* }
* >}
*/
function gestures(node) {
$effect(() => {

@ -22,10 +22,6 @@ The `transition:` directive indicates a _bidirectional_ transition, which means
{/if}
```
## Built-in transitions
A selection of built-in transitions can be imported from the [`svelte/transition`](svelte-transition) module.
## Local vs global
Transitions are local by default. Local transitions only play when the block they belong to is created or destroyed, _not_ when parent blocks are created or destroyed.
@ -40,6 +36,10 @@ Transitions are local by default. Local transitions only play when the block the
{/if}
```
## Built-in transitions
A selection of built-in transitions can be imported from the [`svelte/transition`](svelte-transition) module.
## Transition parameters
Transitions can have parameters.

@ -1,23 +0,0 @@
---
title: class:
---
The `class:` directive is a convenient way to conditionally set classes on elements, as an alternative to using conditional expressions inside `class` attributes:
```svelte
<!-- These are equivalent -->
<div class={isCool ? 'cool' : ''}>...</div>
<div class:cool={isCool}>...</div>
```
As with other directives, we can use a shorthand when the name of the class coincides with the value:
```svelte
<div class:cool>...</div>
```
Multiple `class:` directives can be added to a single element:
```svelte
<div class:cool class:lame={!cool} class:potato>...</div>
```

@ -0,0 +1,102 @@
---
title: class
---
There are two ways to set classes on elements: the `class` attribute, and the `class:` directive.
## Attributes
Primitive values are treated like any other attribute:
```svelte
<div class={large ? 'large' : 'small'}>...</div>
```
> [!NOTE]
> For historical reasons, falsy values (like `false` and `NaN`) are stringified (`class="false"`), though `class={undefined}` (or `null`) cause the attribute to be omitted altogether. In a future version of Svelte, all falsy values will cause `class` to be omitted.
### Objects and arrays
Since Svelte 5.16, `class` can be an object or array, and is converted to a string using [clsx](https://github.com/lukeed/clsx).
If the value is an object, the truthy keys are added:
```svelte
<script>
let { cool } = $props();
</script>
<!-- results in `class="cool"` if `cool` is truthy,
`class="lame"` otherwise -->
<div class={{ cool, lame: !cool }}>...</div>
```
If the value is an array, the truthy values are combined:
```svelte
<!-- if `faded` and `large` are both truthy, results in
`class="saturate-0 opacity-50 scale-200"` -->
<div class={[faded && 'saturate-0 opacity-50', large && 'scale-200']}>...</div>
```
Note that whether we're using the array or object form, we can set multiple classes simultaneously with a single condition, which is particularly useful if you're using things like Tailwind.
Arrays can contain arrays and objects, and clsx will flatten them. This is useful for combining local classes with props, for example:
```svelte
<!--- file: Button.svelte --->
<script>
let props = $props();
</script>
<button {...props} class={['cool-button', props.class]}>
{@render props.children?.()}
</button>
```
The user of this component has the same flexibility to use a mixture of objects, arrays and strings:
```svelte
<!--- file: App.svelte --->
<script>
import Button from './Button.svelte';
let useTailwind = $state(false);
</script>
<Button
onclick={() => useTailwind = true}
class={{ 'bg-blue-700 sm:w-1/2': useTailwind }}
>
Accept the inevitability of Tailwind
</Button>
```
Svelte also exposes the `ClassValue` type, which is the type of value that the `class` attribute on elements accept. This is useful if you want to use a type-safe class name in component props:
```svelte
<script lang="ts">
import type { ClassValue } from 'svelte/elements';
const props: { class: ClassValue } = $props();
</script>
<div class={['original', props.class]}>...</div>
```
## The `class:` directive
Prior to Svelte 5.16, the `class:` directive was the most convenient way to set classes on elements conditionally.
```svelte
<!-- These are equivalent -->
<div class={{ cool, lame: !cool }}>...</div>
<div class:cool={cool} class:lame={!cool}>...</div>
```
As with other directives, we can use a shorthand when the name of the class coincides with the value:
```svelte
<div class:cool class:lame={!cool}>...</div>
```
> [!NOTE] Unless you're using an older version of Svelte, consider avoiding `class:`, since the attribute is more powerful and composable.

@ -1,111 +0,0 @@
---
title: Control flow
---
- if
- each
- await (or move that into some kind of data loading section?)
- NOT: key (move into transition section, because that's the common use case)
Svelte augments HTML with control flow blocks to be able to express conditionally rendered content or lists.
The syntax between these blocks is the same:
- `{#` denotes the start of a block
- `{:` denotes a different branch part of the block. Depending on the block, there can be multiple of these
- `{/` denotes the end of a block
## {#if ...}
## {#each ...}
```svelte
<!--- copy: false --->
{#each expression as name}...{/each}
```
```svelte
<!--- copy: false --->
{#each expression as name, index}...{/each}
```
```svelte
<!--- copy: false --->
{#each expression as name (key)}...{/each}
```
```svelte
<!--- copy: false --->
{#each expression as name, index (key)}...{/each}
```
```svelte
<!--- copy: false --->
{#each expression as name}...{:else}...{/each}
```
Iterating over lists of values can be done with an each block.
```svelte
<h1>Shopping list</h1>
<ul>
{#each items as item}
<li>{item.name} x {item.qty}</li>
{/each}
</ul>
```
You can use each blocks to iterate over any array or array-like value — that is, any object with a `length` property.
An each block can also specify an _index_, equivalent to the second argument in an `array.map(...)` callback:
```svelte
{#each items as item, i}
<li>{i + 1}: {item.name} x {item.qty}</li>
{/each}
```
If a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.
```svelte
{#each items as item (item.id)}
<li>{item.name} x {item.qty}</li>
{/each}
<!-- or with additional index value -->
{#each items as item, i (item.id)}
<li>{i + 1}: {item.name} x {item.qty}</li>
{/each}
```
You can freely use destructuring and rest patterns in each blocks.
```svelte
{#each items as { id, name, qty }, i (id)}
<li>{i + 1}: {name} x {qty}</li>
{/each}
{#each objects as { id, ...rest }}
<li><span>{id}</span><MyComponent {...rest} /></li>
{/each}
{#each items as [id, ...rest]}
<li><span>{id}</span><MyComponent values={rest} /></li>
{/each}
```
An each block can also have an `{:else}` clause, which is rendered if the list is empty.
```svelte
{#each todos as todo}
<p>{todo.text}</p>
{:else}
<p>No tasks today!</p>
{/each}
```
It is possible to iterate over iterables like `Map` or `Set`. Iterables need to be finite and static (they shouldn't change while being iterated over). Under the hood, they are transformed to an array using `Array.from` before being passed off to rendering. If you're writing performance-sensitive code, try to avoid iterables and use regular arrays as they are more performant.
## Other block types
Svelte also provides [`#snippet`](snippets), [`#key`](transitions-and-animations) and [`#await`](data-fetching) blocks. You can find out more about them in their respective sections.

@ -1,20 +0,0 @@
---
title: Data fetching
---
Fetching data is a fundamental part of apps interacting with the outside world. Svelte is unopinionated with how you fetch your data. The simplest way would be using the built-in `fetch` method:
```svelte
<script>
let response = $state();
fetch('/api/data').then(async (r) => (response = r.json()));
</script>
```
While this works, it makes working with promises somewhat unergonomic. Svelte alleviates this problem using the `#await` block.
## {#await ...}
## SvelteKit loaders
Fetching inside your components is great for simple use cases, but it's prone to data loading waterfalls and makes code harder to work with because of the promise handling. SvelteKit solves this problem by providing a opinionated data loading story that is coupled to its router. Learn more about it [in the docs](../kit).

@ -33,10 +33,7 @@ If a component defines `@keyframes`, the name is scoped to the component using t
/* these keyframes are only accessible inside this component */
@keyframes bounce {
/* ... *.
/* ... */
}
</style>
```

@ -13,7 +13,7 @@ Boundaries allow you to guard against errors in part of your app from breaking t
If an error occurs while rendering or updating the children of a `<svelte:boundary>`, or running any [`$effect`]($effect) functions contained therein, the contents will be removed.
Errors occurring outside the rendering process (for example, in event handlers) are _not_ caught by error boundaries.
Errors occurring outside the rendering process (for example, in event handlers or after a `setTimeout` or async work) are _not_ caught by error boundaries.
## Properties

@ -2,129 +2,137 @@
title: Context
---
<!-- - get/set/hasContext
- how to use, best practises (like encapsulating them) -->
Context allows components to access values owned by parent components without passing them down as props (potentially through many layers of intermediate components, known as 'prop-drilling'). The parent component sets context with `setContext(key, value)`...
Most state is component-level state that lives as long as its component lives. There's also section-wide or app-wide state however, which also needs to be handled somehow.
The easiest way to do that is to create global state and just import that.
```svelte
<!--- file: Parent.svelte --->
<script>
import { setContext } from 'svelte';
```ts
/// file: state.svelte.js
export const myGlobalState = $state({
user: {
/* ... */
}
/* ... */
});
setContext('my-context', 'hello from Parent.svelte');
</script>
```
...and the child retrieves it with `getContext`:
```svelte
<!--- file: App.svelte --->
<!--- file: Child.svelte --->
<script>
import { myGlobalState } from './state.svelte.js';
// ...
import { getContext } from 'svelte';
const message = getContext('my-context');
</script>
<h1>{message}, inside Child.svelte</h1>
```
This has a few drawbacks though:
This is particularly useful when `Parent.svelte` is not directly aware of `Child.svelte`, but instead renders it as part of a `children` [snippet](snippet) ([demo](/playground/untitled#H4sIAAAAAAAAE42Q3W6DMAyFX8WyJgESK-oto6hTX2D3YxcM3IIUQpR40yqUd58CrCXsp7tL7HNsf2dAWXaEKR56yfTBGOOxFWQwfR6Qz8q1XAHjL-GjUhvzToJd7bU09FO9ctMkG0wxM5VuFeeFLLjtVK8ZnkpNkuGo-w6CTTJ9Z3PwsBAemlbUF934W8iy5DpaZtOUcU02-ZLcaS51jHEkTFm_kY1_wfOO8QnXrb8hBzDEc6pgZ4gFoyz4KgiD7nxfTe8ghqAhIfrJ46cTzVZBbkPlODVJsLCDO6V7ZcJoncyw1yRr0hd1GNn_ZbEM3I9i1bmVxOlWElUvDUNHxpQngt3C4CXzjS1rtvkw22wMrTRtTbC8Lkuabe7jvthPPe3DofYCAAA=)):
```svelte
<Parent>
<Child />
</Parent>
```
- it only safely works when your global state is only used client-side - for example, when you're building a single page application that does not render any of your components on the server. If your state ends up being managed and updated on the server, it could end up being shared between sessions and/or users, causing bugs
- it may give the false impression that certain state is global when in reality it should only used in a certain part of your app
The key (`'my-context'`, in the example above) and the context itself can be any JavaScript value.
To solve these drawbacks, Svelte provides a few `context` primitives which alleviate these problems.
In addition to [`setContext`](svelte#setContext) and [`getContext`](svelte#getContext), Svelte exposes [`hasContext`](svelte#hasContext) and [`getAllContexts`](svelte#getAllContexts) functions.
## Setting and getting context
## Using context with state
To associate an arbitrary object with the current component, use `setContext`.
You can store reactive state in context ([demo](/playground/untitled#H4sIAAAAAAAAE41R0W6DMAz8FSuaBNUQdK8MkKZ-wh7HHihzu6hgosRMm1D-fUpSVNq12x4iEvvOx_kmQU2PIhfP3DCCJGgHYvxkkYid7NCI_GUS_KUcxhVEMjOelErNB3bsatvG4LW6n0ZsRC4K02qpuKqpZtmrQTNMYJA3QRAs7PTQQxS40eMCt3mX3duxnWb-lS5h7nTI0A4jMWoo4c44P_Hku-zrOazdy64chWo-ScfRkRgl8wgHKrLTH1OxHZkHgoHaTraHcopXUFYzPPVfuC_hwQaD1GrskdiNCdQwJljJqlvXfyqVsA5CGg0uRUQifHw56xFtciO75QrP07vo_JXf_tf8yK2ezDKY_ZWt_1y2qqYzv7bI1IW1V_sN19m-07wCAAA=))...
```svelte
<script>
import { setContext } from 'svelte';
import Child from './Child.svelte';
setContext('key', value);
let counter = $state({
count: 0
});
setContext('counter', counter);
</script>
<button onclick={() => counter.count += 1}>
increment
</button>
<Child />
<Child />
<Child />
```
The context is then available to children of the component (including slotted content) with `getContext`.
...though note that if you _reassign_ `counter` instead of updating it, you will 'break the link' — in other words instead of this...
```svelte
<script>
import { getContext } from 'svelte';
const value = getContext('key');
</script>
<button onclick={() => counter = { count: 0 }}>
reset
</button>
```
`setContext` and `getContext` solve the above problems:
...you must do this:
- the state is not global, it's scoped to the component. That way it's safe to render your components on the server and not leak state
- it's clear that the state is not global but rather scoped to a specific component tree and therefore can't be used in other parts of your app
```svelte
<button onclick={() => +++counter.count = 0+++}>
reset
</button>
```
> [!NOTE] `setContext`/`getContext` must be called during component initialisation.
Svelte will warn you if you get it wrong.
Context is not inherently reactive. If you need reactive values in context then you can pass a `$state` object into context, whose properties _will_ be reactive.
## Type-safe context
```svelte
<!--- file: Parent.svelte --->
<script>
import { setContext } from 'svelte';
A useful pattern is to wrap the calls to `setContext` and `getContext` inside helper functions that let you preserve type safety:
let value = $state({ count: 0 });
setContext('counter', value);
</script>
```js
/// file: context.js
// @filename: ambient.d.ts
interface User {}
<button onclick={() => value.count++}>increment</button>
```
// @filename: index.js
// ---cut---
import { getContext, setContext } from 'svelte';
```svelte
<!--- file: Child.svelte --->
<script>
import { getContext } from 'svelte';
const key = {};
const value = getContext('counter');
</script>
/** @param {User} user */
export function setUserContext(user) {
setContext(key, user);
}
<p>Count is {value.count}</p>
export function getUserContext() {
return /** @type {User} */ (getContext(key));
}
```
To check whether a given `key` has been set in the context of a parent component, use `hasContext`.
## Replacing global state
```svelte
<script>
import { hasContext } from 'svelte';
When you have state shared by many different components, you might be tempted to put it in its own module and just import it wherever it's needed:
if (hasContext('key')) {
// do something
```js
/// file: state.svelte.js
export const myGlobalState = $state({
user: {
// ...
}
</script>
// ...
});
```
You can also retrieve the whole context map that belongs to the closest parent component using `getAllContexts`. This is useful, for example, if you programmatically create a component and want to pass the existing context to it.
In many cases this is perfectly fine, but there is a risk: if you mutate the state during server-side rendering (which is discouraged, but entirely possible!)...
```svelte
<!--- file: App.svelte ---->
<script>
import { getAllContexts } from 'svelte';
import { myGlobalState } from 'svelte';
const contexts = getAllContexts();
let { data } = $props();
if (data.user) {
myGlobalState.user = data.user;
}
</script>
```
## Encapsulating context interactions
The above methods are very unopinionated about how to use them. When your app grows in scale, it's worthwhile to encapsulate setting and getting the context into functions and properly type them.
```ts
// @errors: 2304
import { getContext, setContext } from 'svelte';
let userKey = Symbol('user');
export function setUserContext(user: User) {
setContext(userKey, user);
}
export function getUserContext(): User {
return getContext(userKey) as User;
}
```
...then the data may be accessible by the _next_ user. Context solves this problem because it is not shared between requests.

@ -45,8 +45,6 @@ If a function is returned from `onMount`, it will be called when the component i
## `onDestroy`
> EXPORT_SNIPPET: svelte#onDestroy
Schedules a callback to run immediately before the component is unmounted.
Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the only one that runs inside a server-side component.
@ -149,7 +147,7 @@ With runes, we can use `$effect.pre`, which behaves the same as `$effect` but ru
}
function toggle() {
toggleValue = !toggleValue;
theme = theme === 'dark' ? 'light' : 'dark';
}
</script>

@ -33,19 +33,22 @@ Note that unlike calling `new App(...)` in Svelte 4, things like effects (includ
## `unmount`
Unmounts a component created with [`mount`](#mount) or [`hydrate`](#hydrate):
Unmounts a component that was previously created with [`mount`](#mount) or [`hydrate`](#hydrate).
If `options.outro` is `true`, [transitions](transition) will play before the component is removed from the DOM:
```js
// @errors: 1109
import { mount, unmount } from 'svelte';
import App from './App.svelte';
const app = mount(App, {...});
const app = mount(App, { target: document.body });
// later
unmount(app);
unmount(app, { outro: true });
```
Returns a `Promise` that resolves after transitions have completed if `options.outro` is true, or immediately otherwise.
## `render`
Only available on the server and when compiling with the `server` option. Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app:

@ -40,7 +40,7 @@ You can now write unit tests for code inside your `.js/.ts` files:
/// file: multiplier.svelte.test.js
import { flushSync } from 'svelte';
import { expect, test } from 'vitest';
import { multiplier } from './multiplier.js';
import { multiplier } from './multiplier.svelte.js';
test('Multiplier', () => {
let double = multiplier(0, 2);
@ -53,9 +53,30 @@ test('Multiplier', () => {
});
```
```js
/// file: multiplier.svelte.js
/**
* @param {number} initial
* @param {number} k
*/
export function multiplier(initial, k) {
let count = $state(initial);
return {
get value() {
return count * k;
},
/** @param {number} c */
set: (c) => {
count = c;
}
};
}
```
### Using runes inside your test files
It is possible to use runes inside your test files. First ensure your bundler knows to route the file through the Svelte compiler before running the test by adding `.svelte` to the filename (e.g `multiplier.svelte.test.js`). After that, you can use runes inside your tests.
Since Vitest processes your test files the same way as your source files, you can use runes inside your tests as long as the filename includes `.svelte`:
```js
/// file: multiplier.svelte.test.js
@ -75,6 +96,21 @@ test('Multiplier', () => {
});
```
```js
/// file: multiplier.svelte.js
/**
* @param {() => number} getCount
* @param {number} k
*/
export function multiplier(getCount, k) {
return {
get value() {
return getCount() * k;
}
};
}
```
If the code being tested uses effects, you need to wrap the test inside `$effect.root`:
```js
@ -105,6 +141,27 @@ test('Effect', () => {
});
```
```js
/// file: logger.svelte.js
/**
* @param {() => any} getValue
*/
export function logger(getValue) {
/** @type {any[]} */
let log = $state([]);
$effect(() => {
log.push(getValue());
});
return {
get value() {
return log;
}
};
}
```
### Component testing
It is possible to test your components in isolation using Vitest.

@ -125,3 +125,4 @@ Custom elements can be a useful way to package components for consumption in a n
- The deprecated `let:` directive has no effect, because custom elements do not have a way to pass data to the parent component that fills the slot
- Polyfills are required to support older browsers
- You can use Svelte's context feature between regular Svelte components within a custom element, but you can't use them across custom elements. In other words, you can't use `setContext` on a parent custom element and read that with `getContext` in a child custom element.
- Don't declare properties or attributes starting with `on`, as their usage will be interpreted as an event listener. In other words, Svelte treats `<custom-element oneworld={true}></custom-element>` as `customElement.addEventListener('eworld', true)` (and not as `customElement.oneworld = true`)

@ -10,13 +10,13 @@ You don't have to migrate to the new syntax right away - Svelte 5 still supports
At the heart of Svelte 5 is the new runes API. Runes are basically compiler instructions that inform Svelte about reactivity. Syntactically, runes are functions starting with a dollar-sign.
### let -> $state
### let $state
In Svelte 4, a `let` declaration at the top level of a component was implicitly reactive. In Svelte 5, things are more explicit: a variable is reactive when created using the `$state` rune. Let's migrate the counter to runes mode by wrapping the counter in `$state`:
```svelte
<script>
let count = +++$state(+++0+++)+++;
let count = +++$state(0)+++;
</script>
```
@ -25,14 +25,14 @@ Nothing else changes. `count` is still the number itself, and you read and write
> [!DETAILS] Why we did this
> `let` being implicitly reactive at the top level worked great, but it meant that reactivity was constrained - a `let` declaration anywhere else was not reactive. This forced you to resort to using stores when refactoring code out of the top level of components for reuse. This meant you had to learn an entirely separate reactivity model, and the result often wasn't as nice to work with. Because reactivity is more explicit in Svelte 5, you can keep using the same API outside the top level of components. Head to [the tutorial](/tutorial) to learn more.
### $: -> $derived/$effect
### $: $derived/$effect
In Svelte 4, a `$:` statement at the top level of a component could be used to declare a derivation, i.e. state that is entirely defined through a computation of other state. In Svelte 5, this is achieved using the `$derived` rune:
```svelte
<script>
let count = +++$state(+++0+++)+++;
---$:--- +++const+++ double = +++$derived(+++count * 2+++)+++;
let count = $state(0);
---$:--- +++const+++ double = +++$derived(count * 2)+++;
</script>
```
@ -42,7 +42,8 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is
```svelte
<script>
let count = +++$state(+++0+++)+++;
let count = $state(0);
---$:---+++$effect(() =>+++ {
if (count > 5) {
alert('Count is too high!');
@ -51,6 +52,8 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is
</script>
```
Note that [when `$effect` runs is different]($effect#Understanding-dependencies) than when `$:` runs.
> [!DETAILS] Why we did this
> `$:` was a great shorthand and easy to get started with: you could slap a `$:` in front of most code and it would somehow work. This intuitiveness was also its drawback the more complicated your code became, because it wasn't as easy to reason about. Was the intent of the code to create a derivation, or a side effect? With `$derived` and `$effect`, you have a bit more up-front decision making to do (spoiler alert: 90% of the time you want `$derived`), but future-you and other developers on your team will have an easier time.
>
@ -71,14 +74,14 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is
> - executing dependencies as needed and therefore being immune to ordering problems
> - being TypeScript-friendly
### export let -> $props
### export let $props
In Svelte 4, properties of a component were declared using `export let`. Each property was one declaration. In Svelte 5, all properties are declared through the `$props` rune, through destructuring:
```svelte
<script>
---export let optional = 'unset';
export let required;---
---export let optional = 'unset';---
---export let required;---
+++let { optional = 'unset', required } = $props();+++
</script>
```
@ -103,8 +106,8 @@ In Svelte 5, the `$props` rune makes this straightforward without any additional
```svelte
<script>
---let klass = '';
export { klass as class};---
---let klass = '';---
---export { klass as class};---
+++let { class: klass, ...rest } = $props();+++
</script>
<button class={klass} {...---$$restProps---+++rest+++}>click me</button>
@ -190,9 +193,9 @@ This function is deprecated in Svelte 5. Instead, components should accept _call
```svelte
<!--- file: Pump.svelte --->
<script>
---import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
---
---import { createEventDispatcher } from 'svelte';---
---const dispatch = createEventDispatcher();---
+++let { inflate, deflate } = $props();+++
let power = $state(5);
</script>
@ -339,7 +342,31 @@ When spreading props, local event handlers must go _after_ the spread, or they r
In Svelte 4, content can be passed to components using slots. Svelte 5 replaces them with snippets which are more powerful and flexible, and as such slots are deprecated in Svelte 5.
They continue to work, however, and you can mix and match snippets and slots in your components.
They continue to work, however, and you can pass snippets to a component that uses slots:
```svelte
<!--- file: Child.svelte --->
<slot />
<hr />
<slot name="foo" message="hello" />
```
```svelte
<!--- file: Parent.svelte --->
<script>
import Child from './Child.svelte';
</script>
<Child>
default child content
{#snippet foo({ message })}
message from child: {message}
{/snippet}
</Child>
```
(The reverse is not true — you cannot pass slotted content to a component that uses [`{@render ...}`](/docs/svelte/@render) tags.)
When using custom elements, you should still use `<slot />` like before. In a future version, when Svelte removes its internal version of slots, it will leave those slots as-is, i.e. output a regular DOM tag instead of transforming it.
@ -440,11 +467,11 @@ By now you should have a pretty good understanding of the before/after and how t
We thought the same, which is why we provide a migration script to do most of the migration automatically. You can upgrade your project by using `npx sv migrate svelte-5`. This will do the following things:
- bump core dependencies in your `package.json`
- migrate to runes (`let` -> `$state` etc)
- migrate to event attributes for DOM elements (`on:click` -> `onclick`)
- migrate slot creations to render tags (`<slot />` -> `{@render children()}`)
- migrate slot usages to snippets (`<div slot="x">...</div>` -> `{#snippet x()}<div>...</div>{/snippet}`)
- migrate obvious component creations (`new Component(...)` -> `mount(Component, ...)`)
- migrate to runes (`let` `$state` etc)
- migrate to event attributes for DOM elements (`on:click` `onclick`)
- migrate slot creations to render tags (`<slot />` `{@render children()}`)
- migrate slot usages to snippets (`<div slot="x">...</div>` `{#snippet x()}<div>...</div>{/snippet}`)
- migrate obvious component creations (`new Component(...)` `mount(Component, ...)`)
You can also migrate a single component in VS Code through the `Migrate Component to Svelte 5 Syntax` command, or in our Playground through the `Migrate` button.
@ -597,28 +624,57 @@ export declare const MyComponent: Component<{
To declare that a component of a certain type is required:
```js
import { ComponentA, ComponentB } from 'component-library';
---import type { SvelteComponent } from 'svelte';---
+++import type { Component } from 'svelte';+++
---let C: typeof SvelteComponent<{ foo: string }> = $state(---
+++let C: Component<{ foo: string }> = $state(+++
Math.random() ? ComponentA : ComponentB
);
```
The two utility types `ComponentEvents` and `ComponentType` are also deprecated. `ComponentEvents` is obsolete because events are defined as callback props now, and `ComponentType` is obsolete because the new `Component` type is the component type already (i.e. `ComponentType<SvelteComponent<{ prop: string }>>` is equivalent to `Component<{ prop: string }>`).
### bind:this changes
Because components are no longer classes, using `bind:this` no longer returns a class instance with `$set`, `$on` and `$destroy` methods on it. It only returns the instance exports (`export function/const`) and, if you're using the `accessors` option, a getter/setter-pair for each property.
## `<svelte:component>` is no longer necessary
In Svelte 4, components are _static_ — if you render `<Thing>`, and the value of `Thing` changes, [nothing happens](/playground/7f1fa24f0ab44c1089dcbb03568f8dfa?version=4.2.18). To make it dynamic you had to use `<svelte:component>`.
This is no longer true in Svelte 5:
```svelte
<script lang="ts">
import type { ---SvelteComponent--- +++Component+++ } from 'svelte';
import {
ComponentA,
ComponentB
} from 'component-library';
<script>
import A from './A.svelte';
import B from './B.svelte';
---let component: typeof SvelteComponent<{ foo: string }>---
+++let component: Component<{ foo: string }>+++ = $state(
Math.random() ? ComponentA : ComponentB
);
let Thing = $state();
</script>
<svelte:component this={component} foo="bar" />
<select bind:value={Thing}>
<option value={A}>A</option>
<option value={B}>B</option>
</select>
<!-- these are equivalent -->
<Thing />
<svelte:component this={Thing} />
```
While migrating, keep in mind that your component's name should be capitalized (`Thing`) to distinguish it from elements, unless using dot notation.
The two utility types `ComponentEvents` and `ComponentType` are also deprecated. `ComponentEvents` is obsolete because events are defined as callback props now, and `ComponentType` is obsolete because the new `Component` type is the component type already (e.g. `ComponentType<SvelteComponent<{ prop: string }>>` == `Component<{ prop: string }>`).
### Dot notation indicates a component
### bind:this changes
In Svelte 4, `<foo.bar>` would create an element with a tag name of `"foo.bar"`. In Svelte 5, `foo.bar` is treated as a component instead. This is particularly useful inside `each` blocks:
Because components are no longer classes, using `bind:this` no longer returns a class instance with `$set`, `$on` and `$destroy` methods on it. It only returns the instance exports (`export function/const`) and, if you're using the `accessors` option, a getter/setter-pair for each property.
```svelte
{#each items as item}
<item.component {...item.props} />
{/each}
```
## Whitespace handling changed
@ -653,16 +709,6 @@ The `legacy` compiler option, which generated bulkier but IE-friendly code, no l
Content inside component tags becomes a snippet prop called `children`. You cannot have a separate prop by that name.
## Dot notation indicates a component
In Svelte 4, `<foo.bar>` would create an element with a tag name of `"foo.bar"`. In Svelte 5, `foo.bar` is treated as a component instead. This is particularly useful inside `each` blocks:
```svelte
{#each items as item}
<item.component {...item.props} />
{/each}
```
## Breaking changes in runes mode
Some breaking changes only apply once your component is in runes mode.
@ -679,51 +725,59 @@ If a bindable property has a default value (e.g. `let { foo = $bindable('bar') }
### `accessors` option is ignored
Setting the `accessors` option to `true` makes properties of a component directly accessible on the component instance. In runes mode, properties are never accessible on the component instance. You can use component exports instead if you need to expose them.
Setting the `accessors` option to `true` makes properties of a component directly accessible on the component instance.
### `immutable` option is ignored
Setting the `immutable` option has no effect in runes mode. This concept is replaced by how `$state` and its variations work.
```svelte
<svelte:options accessors={true} />
### Classes are no longer "auto-reactive"
<script>
// available via componentInstance.name
export let name;
</script>
```
In Svelte 4, doing the following triggered reactivity:
In runes mode, properties are never accessible on the component instance. You can use component exports instead if you need to expose them.
```svelte
<script>
let foo = new Foo();
let { name } = $props();
// available via componentInstance.getName()
export const getName = () => name;
</script>
```
<button on:click={() => (foo.value = 1)}>{foo.value}</button
>
Alternatively, if the place where they are instantiated is under your control, you can also make use of runes inside `.js/.ts` files by adjusting their ending to include `.svelte`, i.e. `.svelte.js` or `.svelte.ts`, and then use `$state`:
```js
+++import { mount } from 'svelte';+++
import App from './App.svelte'
---const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
app.foo = 'baz'---
+++const props = $state({ foo: 'bar' });
const app = mount(App, { target: document.getElementById("app"), props });
props.foo = 'baz';+++
```
This is because the Svelte compiler treated the assignment to `foo.value` as an instruction to update anything that referenced `foo`. In Svelte 5, reactivity is determined at runtime rather than compile time, so you should define `value` as a reactive `$state` field on the `Foo` class. Wrapping `new Foo()` with `$state(...)` will have no effect — only vanilla objects and arrays are made deeply reactive.
### `immutable` option is ignored
### `<svelte:component>` is no longer necessary
Setting the `immutable` option has no effect in runes mode. This concept is replaced by how `$state` and its variations work.
In Svelte 4, components are _static_ — if you render `<Thing>`, and the value of `Thing` changes, [nothing happens](/playground/7f1fa24f0ab44c1089dcbb03568f8dfa?version=4.2.18). To make it dynamic you must use `<svelte:component>`.
### Classes are no longer "auto-reactive"
This is no longer true in Svelte 5:
In Svelte 4, doing the following triggered reactivity:
```svelte
<script>
import A from './A.svelte';
import B from './B.svelte';
let Thing = $state();
let foo = new Foo();
</script>
<select bind:value={Thing}>
<option value={A}>A</option>
<option value={B}>B</option>
</select>
<!-- these are equivalent -->
<Thing />
<svelte:component this={Thing} />
<button on:click={() => (foo.value = 1)}>{foo.value}</button
>
```
This is because the Svelte compiler treated the assignment to `foo.value` as an instruction to update anything that referenced `foo`. In Svelte 5, reactivity is determined at runtime rather than compile time, so you should define `value` as a reactive `$state` field on the `Foo` class. Wrapping `new Foo()` with `$state(...)` will have no effect — only vanilla objects and arrays are made deeply reactive.
### Touch and wheel events are passive
When using `onwheel`, `onmousewheel`, `ontouchstart` and `ontouchmove` event attributes, the handlers are [passive](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) to align with browser defaults. This greatly improves responsiveness by allowing the browser to scroll the document immediately, rather than waiting to see if the event handler calls `event.preventDefault()`.

@ -46,7 +46,7 @@ It will show up on hover.
- You can use markdown here.
- You can also use code blocks here.
- Usage:
```tsx
```svelte
<main name="Arethra">
```
-->
@ -96,7 +96,7 @@ However, you can use any router library. A lot of people use [page.js](https://g
If you prefer a declarative HTML approach, there's the isomorphic [svelte-routing](https://github.com/EmilTholin/svelte-routing) library and a fork of it called [svelte-navigator](https://github.com/mefechoel/svelte-navigator) containing some additional functionality.
If you need hash-based routing on the client side, check out [svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router) or [abstract-state-router](https://github.com/TehShrike/abstract-state-router/).
If you need hash-based routing on the client side, check out the [hash option](https://svelte.dev/docs/kit/configuration#router) in SvelteKit, [svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router), or [abstract-state-router](https://github.com/TehShrike/abstract-state-router/).
[Routify](https://routify.dev) is another filesystem-based router, similar to SvelteKit's router. Version 3 supports Svelte's native SSR.

@ -1,6 +0,0 @@
---
title: Reactivity in depth
---
- how to think about Runes ("just JavaScript" with added reactivity, what this means for keeping reactivity alive across boundaries)
- signals

@ -21,15 +21,19 @@ A component is attempting to bind to a non-bindable property `%key%` belonging t
### component_api_changed
```
%parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5. See https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes for more information
Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5
```
See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.
### component_api_invalid_new
```
Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working. See https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes for more information
Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working.
```
See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.
### derived_references_self
```
@ -118,14 +122,39 @@ Property descriptors defined on `$state` objects must contain `value` and always
Cannot set prototype of `$state` object
```
### state_unsafe_local_read
### state_unsafe_mutation
```
Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
```
### state_unsafe_mutation
This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go:
```svelte
<script>
let count = $state(0);
let even = $state(true);
let odd = $derived.by(() => {
even = count % 2 === 0;
return !even;
});
</script>
<button onclick={() => count++}>{count}</button>
<p>{count} is even: {even}</p>
<p>{count} is odd: {odd}</p>
```
Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
This is forbidden because it introduces instability: if `<p>{count} is even: {even}</p>` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
```js
let count = 0;
// ---cut---
let even = $derived(count % 2 === 0);
let odd = $derived(!even);
```
If side-effects are unavoidable, use [`$effect`]($effect) instead.

@ -52,7 +52,7 @@ Your `console.%method%` contained `$state` proxies. Consider using `$inspect(...
When logging a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), browser devtools will log the proxy itself rather than the value it represents. In the case of Svelte, the 'target' of a `$state` proxy might not resemble its current value, which can be confusing.
The easiest way to log a value as it changes over time is to use the [`$inspect`](https://svelte.dev/docs/svelte/$inspect) rune. Alternatively, to log things on a one-off basis (for example, inside an event handler) you can use [`$state.snapshot`](https://svelte.dev/docs/svelte/$state#$state.snapshot) to take a snapshot of the current value.
The easiest way to log a value as it changes over time is to use the [`$inspect`](/docs/svelte/$inspect) rune. Alternatively, to log things on a one-off basis (for example, inside an event handler) you can use [`$state.snapshot`](/docs/svelte/$state#$state.snapshot) to take a snapshot of the current value.
### event_handler_invalid
@ -66,6 +66,31 @@ The easiest way to log a value as it changes over time is to use the [`$inspect`
The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value
```
Certain attributes like `src` on an `<img>` element will not be repaired during hydration, i.e. the server value will be kept. That's because updating these attributes can cause the image to be refetched (or in the case of an `<iframe>`, for the frame to be reloaded), even if they resolve to the same resource.
To fix this, either silence the warning with a [`svelte-ignore`](basic-markup#Comments) comment, or ensure that the value stays the same between server and client. If you really need the value to change on hydration, you can force an update like this:
```svelte
<script>
let { src } = $props();
if (typeof window !== 'undefined') {
// stash the value...
const initial = src;
// unset it...
src = undefined;
$effect(() => {
// ...and reset after we've mounted
src = initial;
});
}
</script>
<img {src} />
```
### hydration_html_changed
```
@ -76,6 +101,31 @@ The value of an `{@html ...}` block changed between server and client renders. T
The value of an `{@html ...}` block %location% changed between server and client renders. The client value will be ignored in favour of the server value
```
If the `{@html ...}` value changes between the server and the client, it will not be repaired during hydration, i.e. the server value will be kept. That's because change detection during hydration is expensive and usually unnecessary.
To fix this, either silence the warning with a [`svelte-ignore`](basic-markup#Comments) comment, or ensure that the value stays the same between server and client. If you really need the value to change on hydration, you can force an update like this:
```svelte
<script>
let { markup } = $props();
if (typeof window !== 'undefined') {
// stash the value...
const initial = markup;
// unset it...
markup = undefined;
$effect(() => {
// ...and reset after we've mounted
markup = initial;
});
}
</script>
{@html markup}
```
### hydration_mismatch
```
@ -86,6 +136,10 @@ Hydration failed because the initial UI does not match what was rendered on the
Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
```
This warning is thrown when Svelte encounters an error while hydrating the HTML from the server. During hydration, Svelte walks the DOM, expecting a certain structure. If that structure is different (for example because the HTML was repaired by the DOM because of invalid HTML), then Svelte will run into issues, resulting in this warning.
During development, this error is often preceeded by a `console.error` detailing the offending HTML, which needs fixing.
### invalid_raw_snippet_render
```
@ -107,58 +161,44 @@ Tried to unmount a component that was not mounted
### ownership_invalid_binding
```
%parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent%
%parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`)
```
### ownership_invalid_mutation
Consider three components `GrandParent`, `Parent` and `Child`. If you do `<GrandParent bind:value>`, inside `GrandParent` pass on the variable via `<Parent {value} />` (note the missing `bind:`) and then do `<Child bind:value>` inside `Parent`, this warning is thrown.
```
Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
```
```
%component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
```
To fix it, `bind:` to the value instead of just passing a property (i.e. in this example do `<Parent bind:value />`).
### reactive_declaration_non_reactive_property
### ownership_invalid_mutation
```
A `$:` statement (%location%) read reactive state that was not visible to the compiler. Updates to this state will not cause the statement to re-run. The behaviour of this code will change if you migrate it to runes mode
Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead
```
In legacy mode, a `$:` [reactive statement](https://svelte.dev/docs/svelte/legacy-reactive-assignments) re-runs when the state it _references_ changes. This is determined at compile time, by analysing the code.
In runes mode, effects and deriveds re-run when there are changes to the values that are read during the function's _execution_.
Consider the following code:
Often, the result is the same — for example these can be considered equivalent:
```svelte
<!--- file: App.svelte --->
<script>
import Child from './Child.svelte';
let person = $state({ name: 'Florida', surname: 'Man' });
</script>
```js
let a = 1, b = 2, sum = 3;
// ---cut---
$: sum = a + b;
<Child {person} />
```
```js
let a = 1, b = 2;
// ---cut---
const sum = $derived(a + b);
```
In some cases — such as the one that triggered the above warning — they are _not_ the same:
```js
let a = 1, b = 2, sum = 3;
// ---cut---
const add = () => a + b;
```svelte
<!--- file: Child.svelte --->
<script>
let { person } = $props();
</script>
// the compiler can't 'see' that `sum` depends on `a` and `b`, but
// they _would_ be read while executing the `$derived` version
$: sum = add();
<input bind:value={person.name}>
<input bind:value={person.surname}>
```
Similarly, reactive properties of [deep state](https://svelte.dev/docs/svelte/$state#Deep-state) are not visible to the compiler. As such, changes to these properties will cause effects and deriveds to re-run but will _not_ cause `$:` statements to re-run.
`Child` is mutating `person` which is owned by `App` without being explicitly "allowed" to do so. This is strongly discouraged since it can create code that is hard to reason about at scale ("who mutated this value?"), hence the warning.
When you [migrate this component](https://svelte.dev/docs/svelte/v5-migration-guide) to runes mode, the behaviour will change accordingly.
To fix it, either create callback props to communicate changes, or mark `person` as [`$bindable`]($bindable).
### state_proxy_equality_mismatch
@ -178,3 +218,15 @@ Reactive `$state(...)` proxies and the values they proxy have different identiti
```
To resolve this, ensure you're comparing values where both values were created with `$state(...)`, or neither were. Note that `$state.raw(...)` will _not_ create a state proxy.
### transition_slide_display
```
The `slide` transition does not work correctly for elements with `display: %value%`
```
The [slide](/docs/svelte/svelte-transition#slide) transition works by animating the `height` of the element, which requires a `display` style like `block`, `flex` or `grid`. It does not work for:
- `display: inline` (which is the default for elements like `<span>`), and its variants like `inline-block`, `inline-flex` and `inline-grid`
- `display: table` and `table-[name]`, which are the defaults for elements like `<table>` and `<tr>`
- `display: contents`

@ -78,10 +78,22 @@ Sequence expressions are not allowed as attribute/directive values in runes mode
Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression
```
### bind_group_invalid_expression
```
`bind:group` can only bind to an Identifier or MemberExpression
```
### bind_group_invalid_snippet_parameter
```
Cannot `bind:group` to a snippet parameter
```
### bind_invalid_expression
```
Can only bind to an Identifier or MemberExpression
Can only bind to an Identifier or MemberExpression or a `{get, set}` pair
```
### bind_invalid_name
@ -94,6 +106,12 @@ Can only bind to an Identifier or MemberExpression
`bind:%name%` is not a valid binding. %explanation%
```
### bind_invalid_parens
```
`bind:%name%={get, set}` must not have surrounding parentheses
```
### bind_invalid_target
```
@ -175,7 +193,7 @@ Cyclical dependency detected: %cycle%
### const_tag_invalid_placement
```
`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>` or `<Component>`
`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>`, `<svelte:boundary` or `<Component>`
```
### constant_assignment
@ -319,7 +337,39 @@ The $ prefix is reserved, and cannot be used for variables and imports
### each_item_invalid_assignment
```
Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)
Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)
```
In legacy mode, it was possible to reassign or bind to the each block argument itself:
```svelte
<script>
let array = [1, 2, 3];
</script>
{#each array as entry}
<!-- reassignment -->
<button on:click={() => entry = 4}>change</button>
<!-- binding -->
<input bind:value={entry}>
{/each}
```
This turned out to be buggy and unpredictable, particularly when working with derived values (such as `array.map(...)`), and as such is forbidden in runes mode. You can achieve the same outcome by using the index instead:
```svelte
<script>
let array = $state([1, 2, 3]);
</script>
{#each array as entry, i}
<!-- reassignment -->
<button onclick={() => array[i] = 4}>change</button>
<!-- binding -->
<input bind:value={array[i]}>
{/each}
```
### effect_invalid_placement
@ -430,6 +480,18 @@ Expected whitespace
Imports of `svelte/internal/*` are forbidden. It contains private runtime code which is subject to change without notice. If you're importing from `svelte/internal/*` to work around a limitation of Svelte, please open an issue at https://github.com/sveltejs/svelte and explain your use case
```
### inspect_trace_generator
```
`$inspect.trace(...)` cannot be used inside a generator function
```
### inspect_trace_invalid_placement
```
`$inspect.trace(...)` must be the first statement of a function body
```
### invalid_arguments_usage
```
@ -517,7 +579,13 @@ Unrecognised compiler option %keypath%
### props_duplicate
```
Cannot use `$props()` more than once
Cannot use `%rune%()` more than once
```
### props_id_invalid_placement
```
`$props.id()` can only be used at the top level of components as a variable declaration initializer
```
### props_illegal_name
@ -592,6 +660,12 @@ Cannot access a computed property of a rune
`%name%` is not a valid rune
```
### rune_invalid_spread
```
`%rune%` cannot be called with a spread argument
```
### rune_invalid_usage
```
@ -988,6 +1062,12 @@ Unexpected end of input
'%word%' is a reserved word in JavaScript and cannot be used here
```
### unterminated_string_constant
```
Unterminated string constant
```
### void_element_invalid_content
```

@ -62,7 +62,7 @@ Enforce that `autofocus` is not used on elements. Autofocusing elements can caus
### a11y_click_events_have_key_events
```
Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type="button">` or `<a>` might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details
Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type="button">` or `<a>` might be more appropriate
```
Enforce that visible, non-interactive elements with an `onclick` event are accompanied by a keyboard event handler.
@ -610,18 +610,60 @@ Empty block
Unused CSS selector "%name%"
```
Svelte traverses both the template and the `<style>` tag to find out which of the CSS selectors are not used within the template, so it can remove them.
In some situations a selector may target an element that is not 'visible' to the compiler, for example because it is part of an `{@html ...}` tag or you're overriding styles in a child component. In these cases, use [`:global`](/docs/svelte/global-styles) to preserve the selector as-is:
```svelte
<div class="post">{@html content}</div>
<style>
.post :global {
p {...}
}
</style>
```
### element_invalid_self_closing_tag
```
Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...></%name%>` rather than `<%name% ... />`
```
In HTML, there's [no such thing as a self-closing tag](https://jakearchibald.com/2023/against-self-closing-tags-in-html/). While this _looks_ like a self-contained element with some text next to it...
```html
<div>
<span class="icon" /> some text!
</div>
```
...a spec-compliant HTML parser (such as a browser) will in fact parse it like this, with the text _inside_ the icon:
```html
<div>
<span class="icon"> some text! </span>
</div>
```
Some templating languages (including Svelte) will 'fix' HTML by turning `<span />` into `<span></span>`. Others adhere to the spec. Both result in ambiguity and confusion when copy-pasting code between different contexts, and as such Svelte prompts you to resolve the ambiguity directly by having an explicit closing tag.
To automate this, run the dedicated migration:
```bash
npx sv migrate self-closing-tags
```
In a future version of Svelte, self-closing tags may be upgraded from a warning to an error.
### event_directive_deprecated
```
Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead
```
See [the migration guide](v5-migration-guide#Event-changes) for more info.
### export_let_unused
```
@ -640,6 +682,8 @@ Component has unused export property '%name%'. If it is for external reference o
Svelte 5 components are no longer classes. Instantiate them using `mount` or `hydrate` (imported from 'svelte') instead.
```
See the [migration guide](v5-migration-guide#Components-are-no-longer-classes) for more info.
### node_invalid_placement_ssr
```
@ -660,6 +704,30 @@ This code will work when the component is rendered on the client (which is why t
`%name%` is updated, but is not declared with `$state(...)`. Changing its value will not correctly trigger updates
```
This warning is thrown when the compiler detects the following:
- a variable was declared without `$state` or `$state.raw`
- the variable is reassigned
- the variable is read in a reactive context
In this case, changing the value will not correctly trigger updates. Example:
```svelte
<script>
let reactive = $state('reactive');
let stale = 'stale';
</script>
<p>This value updates: {reactive}</p>
<p>This value does not update: {stale}</p>
<button onclick={() => {
stale = 'updated';
reactive = 'updated';
}}>update</button>
```
To fix this, wrap your variable declaration with `$state`.
### options_deprecated_accessors
```
@ -732,6 +800,12 @@ Reassignments of module-level declarations will not cause reactive statements to
`context="module"` is deprecated, use the `module` attribute instead
```
```svelte
<script ---context="module"--- +++module+++>
let foo = 'bar';
</script>
```
### script_unknown_attribute
```
@ -744,12 +818,80 @@ Unrecognized attribute — should be one of `generics`, `lang` or `module`. If t
Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead
```
See [the migration guide](v5-migration-guide#Snippets-instead-of-slots) for more info.
### state_referenced_locally
```
State referenced in its own scope will never update. Did you mean to reference it inside a closure?
This reference only captures the initial value of `%name%`. Did you mean to reference it inside a %type% instead?
```
This warning is thrown when the compiler detects the following:
- A reactive variable is declared
- ...and later reassigned...
- ...and referenced in the same scope
This 'breaks the link' to the original state declaration. For example, if you pass the state to a function, the function loses access to the state once it is reassigned:
```svelte
<!--- file: Parent.svelte --->
<script>
import { setContext } from 'svelte';
let count = $state(0);
// warning: state_referenced_locally
setContext('count', count);
</script>
<button onclick={() => count++}>
increment
</button>
```
```svelte
<!--- file: Child.svelte --->
<script>
import { getContext } from 'svelte';
const count = getContext('count');
</script>
<!-- This will never update -->
<p>The count is {count}</p>
```
To fix this, reference the variable such that it is lazily evaluated. For the above example, this can be achieved by wrapping `count` in a function:
```svelte
<!--- file: Parent.svelte --->
<script>
import { setContext } from 'svelte';
let count = $state(0);
setContext('count', +++() => count+++);
</script>
<button onclick={() => count++}>
increment
</button>
```
```svelte
<!--- file: Child.svelte --->
<script>
import { getContext } from 'svelte';
const count = getContext('count');
</script>
<!-- This will update -->
<p>The count is {+++count()+++}</p>
```
For more info, see [Passing state into functions]($state#Passing-state-into-functions).
### store_rune_conflict
```
@ -805,6 +947,8 @@ A derived value may be used in other contexts:
`<svelte:self>` is deprecated — use self-imports (e.g. `import %name% from './%basename%'`) instead
```
See [the note in the docs](legacy-svelte-self) for more info.
### unknown_code
```

@ -5,3 +5,5 @@
```
`%name%(...)` is not available on the server
```
Certain methods such as `mount` cannot be invoked while running in a server context. Avoid calling them eagerly, i.e. not during render.

@ -6,12 +6,60 @@
Cannot use `{@render children(...)}` if the parent component uses `let:` directives. Consider using a named snippet instead
```
This error would be thrown in a setup like this:
```svelte
<!--- file: Parent.svelte --->
<List {items} let:entry>
<span>{entry}</span>
</List>
```
```svelte
<!--- file: List.svelte --->
<script>
let { items, children } = $props();
</script>
<ul>
{#each items as item}
<li>{@render children(item)}</li>
{/each}
</ul>
```
Here, `List.svelte` is using `{@render children(item)` which means it expects `Parent.svelte` to use snippets. Instead, `Parent.svelte` uses the deprecated `let:` directive. This combination of APIs is incompatible, hence the error.
### invalid_snippet_arguments
```
A snippet function was passed invalid arguments. Snippets should only be instantiated via `{@render ...}`
```
### lifecycle_outside_component
```
`%name%(...)` can only be used during component initialisation
```
Certain lifecycle methods can only be used during component initialisation. To fix this, make sure you're invoking the method inside the _top level of the instance script_ of your component.
```svelte
<script>
import { onMount } from 'svelte';
function handleClick() {
// This is wrong
onMount(() => {})
}
// This is correct
onMount(() => {})
</script>
<button onclick={handleClick}>click me</button>
```
### store_invalid_shape
```

@ -6,6 +6,8 @@
`<svelte:element this="%tag%">` is a void element — it cannot have content
```
Elements such as `<input>` cannot have content, any children passed to these elements will be ignored.
### state_snapshot_uncloneable
```
@ -17,3 +19,10 @@ The following properties cannot be cloned with `$state.snapshot` — the return
%properties%
```
`$state.snapshot` tries to clone the given value in order to return a reference that no longer changes. Certain objects may not be cloneable, in which case the original value is returned. In the following example, `property` is cloned, but `window` is not, because DOM elements are uncloneable:
```js
const object = $state({ property: 'this is cloneable', window })
const snapshot = $state.snapshot(object);
```

@ -0,0 +1,15 @@
---
title: svelte/reactivity/window
---
This module exports reactive versions of various `window` values, each of which has a reactive `current` property that you can reference in reactive contexts (templates, [deriveds]($derived) and [effects]($effect)) without using [`<svelte:window>`](svelte-window) bindings or manually creating your own event listeners.
```svelte
<script>
import { innerWidth, innerHeight } from 'svelte/reactivity/window';
</script>
<p>{innerWidth.current}x{innerHeight.current}</p>
```
> MODULE: svelte/reactivity/window

@ -2,7 +2,7 @@
title: <svelte:component>
---
In runes mode, `<MyComponent>` will re-render if the value of `MyComponent` changes.
In runes mode, `<MyComponent>` will re-render if the value of `MyComponent` changes. See the [Svelte 5 migration guide](/docs/svelte/v5-migration-guide#svelte:component-is-no-longer-necessary) for an example.
In legacy mode, it won't — we must use `<svelte:component>`, which destroys and recreates the component instance when the value of its `this` expression changes:

@ -5,9 +5,9 @@
"private": true,
"type": "module",
"license": "MIT",
"packageManager": "pnpm@9.4.0",
"packageManager": "pnpm@10.4.0",
"engines": {
"pnpm": "^9.0.0"
"pnpm": ">=9.0.0"
},
"repository": {
"type": "git",
@ -42,8 +42,8 @@
"prettier-plugin-svelte": "^3.1.2",
"svelte": "workspace:^",
"typescript": "^5.5.4",
"typescript-eslint": "^8.2.0",
"typescript-eslint": "^8.24.0",
"v8-natives": "^1.2.5",
"vitest": "^2.0.5"
"vitest": "^2.1.9"
}
}

@ -1,5 +1,732 @@
# svelte
## 5.26.1
### Patch Changes
- fix: update `state_referenced_locally` message ([#15733](https://github.com/sveltejs/svelte/pull/15733))
## 5.26.0
### Minor Changes
- feat: add `css.hasGlobal` to `compile` output ([#15450](https://github.com/sveltejs/svelte/pull/15450))
### Patch Changes
- fix: add snippet argument validation in dev ([#15521](https://github.com/sveltejs/svelte/pull/15521))
## 5.25.12
### Patch Changes
- fix: improve internal_set versioning mechanic ([#15724](https://github.com/sveltejs/svelte/pull/15724))
- fix: don't transform reassigned state in labeled statement in `$derived` ([#15725](https://github.com/sveltejs/svelte/pull/15725))
## 5.25.11
### Patch Changes
- fix: handle hydration mismatches in await blocks ([#15708](https://github.com/sveltejs/svelte/pull/15708))
- fix: prevent ownership warnings if the fallback of a bindable is used ([#15720](https://github.com/sveltejs/svelte/pull/15720))
## 5.25.10
### Patch Changes
- fix: set deriveds as `CLEAN` if they are assigned to ([#15592](https://github.com/sveltejs/svelte/pull/15592))
- fix: better scope `:global()` with nesting selector `&` ([#15671](https://github.com/sveltejs/svelte/pull/15671))
## 5.25.9
### Patch Changes
- fix: allow `$.state` and `$.derived` to be treeshaken ([#15702](https://github.com/sveltejs/svelte/pull/15702))
- fix: rework binding ownership validation ([#15678](https://github.com/sveltejs/svelte/pull/15678))
## 5.25.8
### Patch Changes
- fix: address untracked_writes memory leak ([#15694](https://github.com/sveltejs/svelte/pull/15694))
## 5.25.7
### Patch Changes
- fix: ensure clearing of old values happens independent of root flushes ([#15664](https://github.com/sveltejs/svelte/pull/15664))
## 5.25.6
### Patch Changes
- fix: ignore generic type arguments while creating AST ([#15659](https://github.com/sveltejs/svelte/pull/15659))
- fix: better consider component and its snippets during css pruning ([#15630](https://github.com/sveltejs/svelte/pull/15630))
## 5.25.5
### Patch Changes
- fix: add setters to `$derived` class properties ([#15628](https://github.com/sveltejs/svelte/pull/15628))
- fix: silence assignment warning on more function bindings ([#15644](https://github.com/sveltejs/svelte/pull/15644))
- fix: make sure CSS is preserved during SSR with bindings ([#15645](https://github.com/sveltejs/svelte/pull/15645))
## 5.25.4
### Patch Changes
- fix: support TS type assertions ([#15642](https://github.com/sveltejs/svelte/pull/15642))
- fix: ensure `undefined` class still applies scoping class, if necessary ([#15643](https://github.com/sveltejs/svelte/pull/15643))
## 5.25.3
### Patch Changes
- fix: prevent state runes from being called with spread ([#15585](https://github.com/sveltejs/svelte/pull/15585))
## 5.25.2
### Patch Changes
- feat: migrate reassigned deriveds to `$derived` ([#15581](https://github.com/sveltejs/svelte/pull/15581))
## 5.25.1
### Patch Changes
- fix: prevent dev server from throwing errors when attempting to retrieve the proxied value of an iframe's contentWindow ([#15577](https://github.com/sveltejs/svelte/pull/15577))
## 5.25.0
### Minor Changes
- feat: make deriveds writable ([#15570](https://github.com/sveltejs/svelte/pull/15570))
## 5.24.1
### Patch Changes
- fix: use `get` in constructor for deriveds ([#15300](https://github.com/sveltejs/svelte/pull/15300))
- fix: ensure toStore root effect is connected to correct parent effect ([#15574](https://github.com/sveltejs/svelte/pull/15574))
## 5.24.0
### Minor Changes
- feat: allow state created in deriveds/effects to be written/read locally without self-invalidation ([#15553](https://github.com/sveltejs/svelte/pull/15553))
### Patch Changes
- fix: check if DOM prototypes are extensible ([#15569](https://github.com/sveltejs/svelte/pull/15569))
- Keep inlined trailing JSDoc comments of properties when running svelte-migrate ([#15567](https://github.com/sveltejs/svelte/pull/15567))
- fix: simplify set calls for proxyable values ([#15548](https://github.com/sveltejs/svelte/pull/15548))
- fix: don't depend on deriveds created inside the current reaction ([#15564](https://github.com/sveltejs/svelte/pull/15564))
## 5.23.2
### Patch Changes
- fix: don't hoist listeners that access non hoistable snippets ([#15534](https://github.com/sveltejs/svelte/pull/15534))
## 5.23.1
### Patch Changes
- fix: invalidate parent effects when child effects update parent dependencies ([#15506](https://github.com/sveltejs/svelte/pull/15506))
- fix: correctly match `:has()` selector during css pruning ([#15277](https://github.com/sveltejs/svelte/pull/15277))
- fix: replace `undefined` with `void 0` to avoid edge case ([#15511](https://github.com/sveltejs/svelte/pull/15511))
- fix: allow global-like pseudo-selectors refinement ([#15313](https://github.com/sveltejs/svelte/pull/15313))
- chore: don't distribute unused types definitions ([#15473](https://github.com/sveltejs/svelte/pull/15473))
- fix: add `files` and `group` to HTMLInputAttributes in elements.d.ts ([#15492](https://github.com/sveltejs/svelte/pull/15492))
- fix: throw rune_invalid_arguments_length when $state.raw() is used with more than 1 arg ([#15516](https://github.com/sveltejs/svelte/pull/15516))
## 5.23.0
### Minor Changes
- fix: make values consistent between effects and their cleanup functions ([#15469](https://github.com/sveltejs/svelte/pull/15469))
## 5.22.6
### Patch Changes
- fix: skip `log_if_contains_state` if only logging literals ([#15468](https://github.com/sveltejs/svelte/pull/15468))
- fix: Add `closedby` property to HTMLDialogAttributes type ([#15458](https://github.com/sveltejs/svelte/pull/15458))
- fix: null and warnings for local handlers ([#15460](https://github.com/sveltejs/svelte/pull/15460))
## 5.22.5
### Patch Changes
- fix: memoize `clsx` calls ([#15456](https://github.com/sveltejs/svelte/pull/15456))
- fix: respect `svelte-ignore hydration_attribute_changed` on elements with spread attributes ([#15443](https://github.com/sveltejs/svelte/pull/15443))
- fix: always use `setAttribute` when setting `style` ([#15323](https://github.com/sveltejs/svelte/pull/15323))
- fix: make `style:` directive and CSS handling more robust ([#15418](https://github.com/sveltejs/svelte/pull/15418))
## 5.22.4
### Patch Changes
- fix: never deduplicate expressions in templates ([#15451](https://github.com/sveltejs/svelte/pull/15451))
## 5.22.3
### Patch Changes
- fix: run effect roots in tree order ([#15446](https://github.com/sveltejs/svelte/pull/15446))
## 5.22.2
### Patch Changes
- fix: correctly set `is_updating` before flushing root effects ([#15442](https://github.com/sveltejs/svelte/pull/15442))
## 5.22.1
### Patch Changes
- chore: switch acorn-typescript plugin ([#15393](https://github.com/sveltejs/svelte/pull/15393))
## 5.22.0
### Minor Changes
- feat: Add `idPrefix` option to `render` ([#15428](https://github.com/sveltejs/svelte/pull/15428))
### Patch Changes
- fix: make dialog element and role interactive ([#15429](https://github.com/sveltejs/svelte/pull/15429))
## 5.21.0
### Minor Changes
- chore: Reduce hydration comment for {:else if} ([#15250](https://github.com/sveltejs/svelte/pull/15250))
### Patch Changes
- fix: disallow `bind:group` to snippet parameters ([#15401](https://github.com/sveltejs/svelte/pull/15401))
## 5.20.5
### Patch Changes
- fix: allow double hyphen css selector names ([#15384](https://github.com/sveltejs/svelte/pull/15384))
- fix: class:directive not working with $restProps #15386 ([#15389](https://github.com/sveltejs/svelte/pull/15389))
fix: spread add an useless cssHash on non-scoped element
- fix: catch error on @const tag in svelte:boundary in DEV mode ([#15369](https://github.com/sveltejs/svelte/pull/15369))
- fix: allow for duplicate `var` declarations ([#15382](https://github.com/sveltejs/svelte/pull/15382))
- fix : bug "$0 is not defined" on svelte:element with a function call on class ([#15396](https://github.com/sveltejs/svelte/pull/15396))
## 5.20.4
### Patch Changes
- fix: update types and inline docs for flushSync ([#15348](https://github.com/sveltejs/svelte/pull/15348))
## 5.20.3
### Patch Changes
- fix: allow `@const` inside `#key` ([#15377](https://github.com/sveltejs/svelte/pull/15377))
- fix: remove unnecessary `?? ''` on some expressions ([#15287](https://github.com/sveltejs/svelte/pull/15287))
- fix: correctly override class attributes with class directives ([#15352](https://github.com/sveltejs/svelte/pull/15352))
## 5.20.2
### Patch Changes
- chore: remove unused `options.uid` in `render` ([#15302](https://github.com/sveltejs/svelte/pull/15302))
- fix: do not warn for `binding_property_non_reactive` if binding is a store in an each ([#15318](https://github.com/sveltejs/svelte/pull/15318))
- fix: prevent writable store value from becoming a proxy when reassigning using $-prefix ([#15283](https://github.com/sveltejs/svelte/pull/15283))
- fix: `muted` reactive without `bind` and select/autofocus attributes working with function calls ([#15326](https://github.com/sveltejs/svelte/pull/15326))
- fix: ensure input elements and elements with `dir` attribute are marked as non-static ([#15259](https://github.com/sveltejs/svelte/pull/15259))
- fix: fire delegated events on target even it was disabled in the meantime ([#15319](https://github.com/sveltejs/svelte/pull/15319))
## 5.20.1
### Patch Changes
- fix: ensure AST analysis on `svelte.js` modules succeeds ([#15297](https://github.com/sveltejs/svelte/pull/15297))
- fix: ignore typescript abstract methods ([#15267](https://github.com/sveltejs/svelte/pull/15267))
- fix: correctly ssr component in `svelte:head` with `$props.id()` or `css='injected'` ([#15291](https://github.com/sveltejs/svelte/pull/15291))
## 5.20.0
### Minor Changes
- feat: SSR-safe ID generation with `$props.id()` ([#15185](https://github.com/sveltejs/svelte/pull/15185))
### Patch Changes
- fix: take private and public into account for `constant_assignment` of derived state ([#15276](https://github.com/sveltejs/svelte/pull/15276))
- fix: value/checked not correctly set using spread ([#15239](https://github.com/sveltejs/svelte/pull/15239))
- chore: tweak effect self invalidation logic, run transition dispatches without reactive context ([#15275](https://github.com/sveltejs/svelte/pull/15275))
- fix: use `importNode` to clone templates for Firefox ([#15272](https://github.com/sveltejs/svelte/pull/15272))
- fix: recurse into `$derived` for ownership validation ([#15166](https://github.com/sveltejs/svelte/pull/15166))
## 5.19.10
### Patch Changes
- fix: when re-connecting unowned deriveds, remove their unowned flag ([#15255](https://github.com/sveltejs/svelte/pull/15255))
- fix: allow mutation of private derived state ([#15228](https://github.com/sveltejs/svelte/pull/15228))
## 5.19.9
### Patch Changes
- fix: ensure unowned derived dependencies are not duplicated when reactions are skipped ([#15232](https://github.com/sveltejs/svelte/pull/15232))
- fix: hydrate `href` that is part of spread attributes ([#15226](https://github.com/sveltejs/svelte/pull/15226))
## 5.19.8
### Patch Changes
- fix: properly set `value` property of custom elements ([#15206](https://github.com/sveltejs/svelte/pull/15206))
- fix: ensure custom element updates don't run in hydration mode ([#15217](https://github.com/sveltejs/svelte/pull/15217))
- fix: ensure tracking returns true, even if in unowned ([#15214](https://github.com/sveltejs/svelte/pull/15214))
## 5.19.7
### Patch Changes
- chore: remove unused code from signal logic ([#15195](https://github.com/sveltejs/svelte/pull/15195))
- fix: encounter svelte:element in blocks as sibling during pruning css ([#15165](https://github.com/sveltejs/svelte/pull/15165))
## 5.19.6
### Patch Changes
- fix: do not prune selectors like `:global(.foo):has(.scoped)` ([#15140](https://github.com/sveltejs/svelte/pull/15140))
- fix: don't error on slot prop inside block inside other component ([#15148](https://github.com/sveltejs/svelte/pull/15148))
- fix: ensure reactions are correctly attached for unowned deriveds ([#15158](https://github.com/sveltejs/svelte/pull/15158))
- fix: silence a11y attribute warnings when spread attributes present ([#15150](https://github.com/sveltejs/svelte/pull/15150))
- fix: prevent false-positive ownership validations due to hot reload ([#15154](https://github.com/sveltejs/svelte/pull/15154))
- fix: widen ownership when calling setContext ([#15153](https://github.com/sveltejs/svelte/pull/15153))
## 5.19.5
### Patch Changes
- fix: improve derived connection to ownership graph ([#15137](https://github.com/sveltejs/svelte/pull/15137))
- fix: correctly look for sibling elements inside blocks and components when pruning CSS ([#15106](https://github.com/sveltejs/svelte/pull/15106))
## 5.19.4
### Patch Changes
- fix: Add `bind:focused` property to `HTMLAttributes` type ([#15122](https://github.com/sveltejs/svelte/pull/15122))
- fix: lazily connect derievds (in deriveds) to their parent ([#15129](https://github.com/sveltejs/svelte/pull/15129))
- fix: disallow $state/$derived in const tags ([#15115](https://github.com/sveltejs/svelte/pull/15115))
## 5.19.3
### Patch Changes
- fix: don't throw for `undefined` non delegated event handlers ([#15087](https://github.com/sveltejs/svelte/pull/15087))
- fix: consistently set value to blank string when value attribute is undefined ([#15057](https://github.com/sveltejs/svelte/pull/15057))
- fix: optimise || expressions in template ([#15092](https://github.com/sveltejs/svelte/pull/15092))
- fix: correctly handle `novalidate` attribute casing ([#15083](https://github.com/sveltejs/svelte/pull/15083))
- fix: expand boolean attribute support ([#15095](https://github.com/sveltejs/svelte/pull/15095))
- fix: avoid double deriveds in component props ([#15089](https://github.com/sveltejs/svelte/pull/15089))
- fix: add check for `is` attribute to correctly detect custom elements ([#15086](https://github.com/sveltejs/svelte/pull/15086))
## 5.19.2
### Patch Changes
- fix: address regression with untrack ([#15079](https://github.com/sveltejs/svelte/pull/15079))
## 5.19.1
### Patch Changes
- fix: omit unnecessary nullish coallescing in template expressions ([#15056](https://github.com/sveltejs/svelte/pull/15056))
- fix: more efficient template effect grouping ([#15050](https://github.com/sveltejs/svelte/pull/15050))
- fix: ensure untrack correctly retains the active reaction ([#15065](https://github.com/sveltejs/svelte/pull/15065))
- fix: initialize `files` bind on hydration ([#15059](https://github.com/sveltejs/svelte/pull/15059))
## 5.19.0
### Minor Changes
- feat: Expose `ClassValue` from `svelte/elements` ([#15035](https://github.com/sveltejs/svelte/pull/15035))
### Patch Changes
- fix: create fewer deriveds for concatenated strings ([#15041](https://github.com/sveltejs/svelte/pull/15041))
- fix: correctly parse leading comments in function binding ([#15020](https://github.com/sveltejs/svelte/pull/15020))
## 5.18.0
### Minor Changes
- feat: allow `<template>` elements to contain any child ([#15007](https://github.com/sveltejs/svelte/pull/15007))
### Patch Changes
- fix: ensure resume effects are scheduled in topological order ([#15012](https://github.com/sveltejs/svelte/pull/15012))
- fix: bump esrap ([#15015](https://github.com/sveltejs/svelte/pull/15015))
- fix: remove listener on `bind_current_time` teardown ([#15013](https://github.com/sveltejs/svelte/pull/15013))
## 5.17.5
### Patch Changes
- feat: allow const tag inside `svelte:boundary` ([#14993](https://github.com/sveltejs/svelte/pull/14993))
- fix: ensure signal write invalidation within effects is consistent ([#14989](https://github.com/sveltejs/svelte/pull/14989))
## 5.17.4
### Patch Changes
- fix: never consider inert boundary effects ([#14999](https://github.com/sveltejs/svelte/pull/14999))
- fix: store access on component destroy ([#14968](https://github.com/sveltejs/svelte/pull/14968))
- fix: correctly transform `pre` with no content ([#14973](https://github.com/sveltejs/svelte/pull/14973))
- fix: wrap each block expression in derived to encapsulate effects ([#14967](https://github.com/sveltejs/svelte/pull/14967))
## 5.17.3
### Patch Changes
- fix: reset dependency read versions after reaction execution ([#14964](https://github.com/sveltejs/svelte/pull/14964))
## 5.17.2
### Patch Changes
- fix: account for parent scale when animating elements ([#14957](https://github.com/sveltejs/svelte/pull/14957))
- fix: apply `overflow: hidden` style when transitioning elements, where necessary ([#14930](https://github.com/sveltejs/svelte/pull/14930))
- fix: properly add owners to function bindings ([#14962](https://github.com/sveltejs/svelte/pull/14962))
## 5.17.1
### Patch Changes
- fix: remove bindable prop validation ([#14946](https://github.com/sveltejs/svelte/pull/14946))
- chore: tweak "invalid assignment" compiler error message ([#14955](https://github.com/sveltejs/svelte/pull/14955))
- fix: silence false-positive stale value warning ([#14958](https://github.com/sveltejs/svelte/pull/14958))
## 5.17.0
### Minor Changes
- feat: allow non-numeric values to be tweened by snapping immediately to new value ([#14941](https://github.com/sveltejs/svelte/pull/14941))
### Patch Changes
- fix: handle default values in object destructuring within "each" blocks when using characters like "}" and "]" ([#14554](https://github.com/sveltejs/svelte/pull/14554))
- fix: account for min-width/height in `slide` transition ([#14942](https://github.com/sveltejs/svelte/pull/14942))
- fix: prevent long delays causing erratic spring behaviour ([#14940](https://github.com/sveltejs/svelte/pull/14940))
- feat: warn on using `slide` transition with table elements ([#14936](https://github.com/sveltejs/svelte/pull/14936))
- chore: improve signal performance by reducing duplicate deps ([#14945](https://github.com/sveltejs/svelte/pull/14945))
## 5.16.6
### Patch Changes
- fix: Make Tween duration 0 set current to target immediately ([#14937](https://github.com/sveltejs/svelte/pull/14937))
- fix: guard against `customElements` being unavailable in browser extension contexts ([#14933](https://github.com/sveltejs/svelte/pull/14933))
- fix: treat `inert` as a boolean attribute ([#14935](https://github.com/sveltejs/svelte/pull/14935))
- fix: remove leading newline from `<pre>` contents ([#14922](https://github.com/sveltejs/svelte/pull/14922))
## 5.16.5
### Patch Changes
- fix: inherit correct namespace for `<title>` elements ([#14817](https://github.com/sveltejs/svelte/pull/14817))
- fix: don't throw `bind_invalid_export` if there's also a bindable prop with the same name ([#14813](https://github.com/sveltejs/svelte/pull/14813))
## 5.16.4
### Patch Changes
- fix: use cached indexOf array prototype method internally ([#14912](https://github.com/sveltejs/svelte/pull/14912))
- fix: make Tween work with continuous target changes ([#14895](https://github.com/sveltejs/svelte/pull/14895))
## 5.16.3
### Patch Changes
- fix: correctly parse `each` with loose parser ([#14887](https://github.com/sveltejs/svelte/pull/14887))
- fix: apply `clsx` logic to custom element `class` attributes ([#14907](https://github.com/sveltejs/svelte/pull/14907))
## 5.16.2
### Patch Changes
- fix: ensure disconnected deriveds correctly connect again ([#14899](https://github.com/sveltejs/svelte/pull/14899))
- fix: correctly highlight sources reassigned inside `trace` ([#14811](https://github.com/sveltejs/svelte/pull/14811))
## 5.16.1
### Patch Changes
- fix: ensure unowned deriveds correctly get re-linked to the graph ([#14855](https://github.com/sveltejs/svelte/pull/14855))
- fix: ensure $inspect.trace works correctly with null values ([#14853](https://github.com/sveltejs/svelte/pull/14853))
## 5.16.0
### Minor Changes
- feat: allow `class` attribute to be an object or array, using `clsx` ([#14714](https://github.com/sveltejs/svelte/pull/14714))
### Patch Changes
- fix: don't include keyframes in global scope in the keyframes to rename ([#14822](https://github.com/sveltejs/svelte/pull/14822))
## 5.15.0
### Minor Changes
- feat: add "worker" exports condition to better support bundling for worker-based environments ([#14779](https://github.com/sveltejs/svelte/pull/14779))
## 5.14.6
### Patch Changes
- fix: treeshake `$inspect.trace` code if unused in modules ([#14774](https://github.com/sveltejs/svelte/pull/14774))
- fix: Improve typescript DX for $inspect, $props, $bindable, and $host ([#14777](https://github.com/sveltejs/svelte/pull/14777))
## 5.14.5
### Patch Changes
- fix: bump esrap dependency ([#14765](https://github.com/sveltejs/svelte/pull/14765))
- fix: ensure svg namespace for `<a>` elements is correct ([#14756](https://github.com/sveltejs/svelte/pull/14756))
- fix: treeshake `$inspect.trace` code if unused ([#14770](https://github.com/sveltejs/svelte/pull/14770))
## 5.14.4
### Patch Changes
- fix: remove implements from class declarations ([#14749](https://github.com/sveltejs/svelte/pull/14749))
- fix: remove unwanted properties from both replaced and unreplaced nodes ([#14744](https://github.com/sveltejs/svelte/pull/14744))
## 5.14.3
### Patch Changes
- fix: bump esrap, prevent malformed AST ([#14742](https://github.com/sveltejs/svelte/pull/14742))
- fix: compare array contents for equality mismatch detections, not the arrays themselves ([#14738](https://github.com/sveltejs/svelte/pull/14738))
## 5.14.2
### Patch Changes
- fix: correctly highlight first rerun of `$inspect.trace` ([#14734](https://github.com/sveltejs/svelte/pull/14734))
- chore: more loose parser improvements ([#14733](https://github.com/sveltejs/svelte/pull/14733))
## 5.14.1
### Patch Changes
- fix: improve unowned derived performance ([#14724](https://github.com/sveltejs/svelte/pull/14724))
## 5.14.0
### Minor Changes
- feat: adds $inspect.trace rune ([#14290](https://github.com/sveltejs/svelte/pull/14290))
## 5.13.0
### Minor Changes
- feat: add `outro` option to `unmount` ([#14540](https://github.com/sveltejs/svelte/pull/14540))
- feat: provide loose parser mode ([#14691](https://github.com/sveltejs/svelte/pull/14691))
## 5.12.0
### Minor Changes
- feat: expose more AST types from `"svelte/compiler"` ([#14601](https://github.com/sveltejs/svelte/pull/14601))
### Patch Changes
- fix: don't add parenthesis to media query if already present ([#14699](https://github.com/sveltejs/svelte/pull/14699))
- fix: ensure if block paths retain correct template namespacing ([#14685](https://github.com/sveltejs/svelte/pull/14685))
## 5.11.3
### Patch Changes
- fix: allow unquoted slash in attributes ([#14615](https://github.com/sveltejs/svelte/pull/14615))
- fix: better handle hydration of script/style elements ([#14683](https://github.com/sveltejs/svelte/pull/14683))
- fix: make `defaultValue` work with spread ([#14640](https://github.com/sveltejs/svelte/pull/14640))
- fix: avoid mutation validation for invalidate_inner_signals ([#14688](https://github.com/sveltejs/svelte/pull/14688))
## 5.11.2
### Patch Changes
- fix: correctly handle ssr for `reactivity/window` ([#14681](https://github.com/sveltejs/svelte/pull/14681))
## 5.11.1
### Patch Changes
- fix: account for global block in `is_empty` ([#14677](https://github.com/sveltejs/svelte/pull/14677))
- fix: remove overzealous `reactive_declaration_non_reactive_property` warning ([#14663](https://github.com/sveltejs/svelte/pull/14663))
## 5.11.0
### Minor Changes
- feat: add `svelte/reactivity/window` module ([#14660](https://github.com/sveltejs/svelte/pull/14660))
### Patch Changes
- fix: take into account registration state when setting custom element props ([#14508](https://github.com/sveltejs/svelte/pull/14508))
## 5.10.1
### Patch Changes
- fix: ensure snippet hoisting works in the correct scope ([#14642](https://github.com/sveltejs/svelte/pull/14642))
- fix: ensure $state.snapshot clones holey arrays correctly ([#14657](https://github.com/sveltejs/svelte/pull/14657))
- fix: restore input binding selection position ([#14649](https://github.com/sveltejs/svelte/pull/14649))
- fix: transform everything that is not a selector inside `:global` ([#14577](https://github.com/sveltejs/svelte/pull/14577))
- Overwrite Spring.#last_value when using .set() with {instant: true} ([#14656](https://github.com/sveltejs/svelte/pull/14656))
- fix: don't emit assignment warnings for bindings ([#14651](https://github.com/sveltejs/svelte/pull/14651))
## 5.10.0
### Minor Changes
- feat: provide links to documentation for errors/warnings ([#14629](https://github.com/sveltejs/svelte/pull/14629))
### Patch Changes
- fix: allow exports with source from script module even if no bind is present ([#14620](https://github.com/sveltejs/svelte/pull/14620))
- fix: deconflict `get_name` for literal class properties ([#14607](https://github.com/sveltejs/svelte/pull/14607))
## 5.9.1
### Patch Changes
- fix: mark subtree dynamic for bind with sequence expressions ([#14626](https://github.com/sveltejs/svelte/pull/14626))
## 5.9.0
### Minor Changes
- feat: add support for bind getters/setters ([#14307](https://github.com/sveltejs/svelte/pull/14307))
### Patch Changes
- fix: always run `if` block code the first time ([#14597](https://github.com/sveltejs/svelte/pull/14597))
## 5.8.1
### Patch Changes
- fix: reinstate missing prefersReducedMotion export ([#14586](https://github.com/sveltejs/svelte/pull/14586))
## 5.8.0
### Minor Changes

@ -1,4 +1,9 @@
[![Cybernetically enhanced web apps: Svelte](https://sveltejs.github.io/assets/banner.png)](https://svelte.dev)
<a href="https://svelte.dev">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="../../assets/banner_dark.png">
<img src="../../assets/banner.png" alt="Svelte - web development for the rest of us" />
</picture>
</a>
[![npm version](https://img.shields.io/npm/v/svelte.svg)](https://www.npmjs.com/package/svelte) [![license](https://img.shields.io/npm/l/svelte.svg)](LICENSE.md) [![Chat](https://img.shields.io/discord/457912077277855764?label=chat&logo=discord)](https://svelte.dev/chat)

@ -741,7 +741,7 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
accesskey?: string | undefined | null;
autocapitalize?: 'characters' | 'off' | 'on' | 'none' | 'sentences' | 'words' | undefined | null;
autofocus?: boolean | undefined | null;
class?: string | undefined | null;
class?: ClassValue | undefined | null;
contenteditable?: Booleanish | 'inherit' | 'plaintext-only' | undefined | null;
contextmenu?: string | undefined | null;
dir?: 'ltr' | 'rtl' | 'auto' | undefined | null;
@ -839,6 +839,7 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
readonly 'bind:contentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:borderBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:devicePixelContentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:focused'?: boolean | undefined | null;
// SvelteKit
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
@ -956,6 +957,7 @@ export interface HTMLDelAttributes extends HTMLAttributes<HTMLModElement> {
export interface HTMLDialogAttributes extends HTMLAttributes<HTMLDialogElement> {
open?: boolean | undefined | null;
closedby?: 'any' | 'closerequest' | 'none' | undefined | null;
}
export interface HTMLEmbedAttributes extends HTMLAttributes<HTMLEmbedElement> {
@ -1074,6 +1076,7 @@ export interface HTMLInputAttributes extends HTMLAttributes<HTMLInputElement> {
checked?: boolean | undefined | null;
dirname?: string | undefined | null;
disabled?: boolean | undefined | null;
files?: FileList | undefined | null;
form?: string | undefined | null;
formaction?: string | undefined | null;
formenctype?:
@ -1085,6 +1088,7 @@ export interface HTMLInputAttributes extends HTMLAttributes<HTMLInputElement> {
formmethod?: 'dialog' | 'get' | 'post' | 'DIALOG' | 'GET' | 'POST' | undefined | null;
formnovalidate?: boolean | undefined | null;
formtarget?: string | undefined | null;
group?: any | undefined | null;
height?: number | string | undefined | null;
indeterminate?: boolean | undefined | null;
list?: string | undefined | null;
@ -1522,7 +1526,7 @@ export interface SvelteWindowAttributes extends HTMLAttributes<Window> {
export interface SVGAttributes<T extends EventTarget> extends AriaAttributes, DOMAttributes<T> {
// Attributes which also defined in HTMLAttributes
className?: string | undefined | null;
class?: string | undefined | null;
class?: ClassValue | undefined | null;
color?: string | undefined | null;
height?: number | string | undefined | null;
id?: string | undefined | null;
@ -2059,3 +2063,5 @@ export interface SvelteHTMLElements {
[name: string]: { [name: string]: any };
}
export type ClassValue = string | import('clsx').ClassArray | import('clsx').ClassDictionary;

@ -12,11 +12,15 @@
## component_api_changed
> %parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5. See https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes for more information
> Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5
See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.
## component_api_invalid_new
> Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working. See https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes for more information
> Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working.
See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.
## derived_references_self
@ -76,10 +80,37 @@
> Cannot set prototype of `$state` object
## state_unsafe_local_read
> Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
## state_unsafe_mutation
> Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go:
```svelte
<script>
let count = $state(0);
let even = $state(true);
let odd = $derived.by(() => {
even = count % 2 === 0;
return !even;
});
</script>
<button onclick={() => count++}>{count}</button>
<p>{count} is even: {even}</p>
<p>{count} is odd: {odd}</p>
```
This is forbidden because it introduces instability: if `<p>{count} is even: {even}</p>` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
```js
let count = 0;
// ---cut---
let even = $derived(count % 2 === 0);
let odd = $derived(!even);
```
If side-effects are unavoidable, use [`$effect`]($effect) instead.

@ -42,7 +42,7 @@ function add() {
When logging a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), browser devtools will log the proxy itself rather than the value it represents. In the case of Svelte, the 'target' of a `$state` proxy might not resemble its current value, which can be confusing.
The easiest way to log a value as it changes over time is to use the [`$inspect`](https://svelte.dev/docs/svelte/$inspect) rune. Alternatively, to log things on a one-off basis (for example, inside an event handler) you can use [`$state.snapshot`](https://svelte.dev/docs/svelte/$state#$state.snapshot) to take a snapshot of the current value.
The easiest way to log a value as it changes over time is to use the [`$inspect`](/docs/svelte/$inspect) rune. Alternatively, to log things on a one-off basis (for example, inside an event handler) you can use [`$state.snapshot`](/docs/svelte/$state#$state.snapshot) to take a snapshot of the current value.
## event_handler_invalid
@ -52,18 +52,72 @@ The easiest way to log a value as it changes over time is to use the [`$inspect`
> The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value
Certain attributes like `src` on an `<img>` element will not be repaired during hydration, i.e. the server value will be kept. That's because updating these attributes can cause the image to be refetched (or in the case of an `<iframe>`, for the frame to be reloaded), even if they resolve to the same resource.
To fix this, either silence the warning with a [`svelte-ignore`](basic-markup#Comments) comment, or ensure that the value stays the same between server and client. If you really need the value to change on hydration, you can force an update like this:
```svelte
<script>
let { src } = $props();
if (typeof window !== 'undefined') {
// stash the value...
const initial = src;
// unset it...
src = undefined;
$effect(() => {
// ...and reset after we've mounted
src = initial;
});
}
</script>
<img {src} />
```
## hydration_html_changed
> The value of an `{@html ...}` block changed between server and client renders. The client value will be ignored in favour of the server value
> The value of an `{@html ...}` block %location% changed between server and client renders. The client value will be ignored in favour of the server value
If the `{@html ...}` value changes between the server and the client, it will not be repaired during hydration, i.e. the server value will be kept. That's because change detection during hydration is expensive and usually unnecessary.
To fix this, either silence the warning with a [`svelte-ignore`](basic-markup#Comments) comment, or ensure that the value stays the same between server and client. If you really need the value to change on hydration, you can force an update like this:
```svelte
<script>
let { markup } = $props();
if (typeof window !== 'undefined') {
// stash the value...
const initial = markup;
// unset it...
markup = undefined;
$effect(() => {
// ...and reset after we've mounted
markup = initial;
});
}
</script>
{@html markup}
```
## hydration_mismatch
> Hydration failed because the initial UI does not match what was rendered on the server
> Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
This warning is thrown when Svelte encounters an error while hydrating the HTML from the server. During hydration, Svelte walks the DOM, expecting a certain structure. If that structure is different (for example because the HTML was repaired by the DOM because of invalid HTML), then Svelte will run into issues, resulting in this warning.
During development, this error is often preceeded by a `console.error` detailing the offending HTML, which needs fixing.
## invalid_raw_snippet_render
> The `render` function passed to `createRawSnippet` should return HTML for a single element
@ -78,51 +132,41 @@ The easiest way to log a value as it changes over time is to use the [`$inspect`
## ownership_invalid_binding
> %parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent%
## ownership_invalid_mutation
> Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
> %component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
> %parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`)
## reactive_declaration_non_reactive_property
Consider three components `GrandParent`, `Parent` and `Child`. If you do `<GrandParent bind:value>`, inside `GrandParent` pass on the variable via `<Parent {value} />` (note the missing `bind:`) and then do `<Child bind:value>` inside `Parent`, this warning is thrown.
> A `$:` statement (%location%) read reactive state that was not visible to the compiler. Updates to this state will not cause the statement to re-run. The behaviour of this code will change if you migrate it to runes mode
To fix it, `bind:` to the value instead of just passing a property (i.e. in this example do `<Parent bind:value />`).
In legacy mode, a `$:` [reactive statement](https://svelte.dev/docs/svelte/legacy-reactive-assignments) re-runs when the state it _references_ changes. This is determined at compile time, by analysing the code.
## ownership_invalid_mutation
In runes mode, effects and deriveds re-run when there are changes to the values that are read during the function's _execution_.
> Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead
Often, the result is the same — for example these can be considered equivalent:
Consider the following code:
```js
let a = 1, b = 2, sum = 3;
// ---cut---
$: sum = a + b;
```
```svelte
<!--- file: App.svelte --->
<script>
import Child from './Child.svelte';
let person = $state({ name: 'Florida', surname: 'Man' });
</script>
```js
let a = 1, b = 2;
// ---cut---
const sum = $derived(a + b);
<Child {person} />
```
In some cases — such as the one that triggered the above warning — they are _not_ the same:
```js
let a = 1, b = 2, sum = 3;
// ---cut---
const add = () => a + b;
```svelte
<!--- file: Child.svelte --->
<script>
let { person } = $props();
</script>
// the compiler can't 'see' that `sum` depends on `a` and `b`, but
// they _would_ be read while executing the `$derived` version
$: sum = add();
<input bind:value={person.name}>
<input bind:value={person.surname}>
```
Similarly, reactive properties of [deep state](https://svelte.dev/docs/svelte/$state#Deep-state) are not visible to the compiler. As such, changes to these properties will cause effects and deriveds to re-run but will _not_ cause `$:` statements to re-run.
`Child` is mutating `person` which is owned by `App` without being explicitly "allowed" to do so. This is strongly discouraged since it can create code that is hard to reason about at scale ("who mutated this value?"), hence the warning.
When you [migrate this component](https://svelte.dev/docs/svelte/v5-migration-guide) to runes mode, the behaviour will change accordingly.
To fix it, either create callback props to communicate changes, or mark `person` as [`$bindable`]($bindable).
## state_proxy_equality_mismatch
@ -140,3 +184,13 @@ When you [migrate this component](https://svelte.dev/docs/svelte/v5-migration-gu
```
To resolve this, ensure you're comparing values where both values were created with `$state(...)`, or neither were. Note that `$state.raw(...)` will _not_ create a state proxy.
## transition_slide_display
> The `slide` transition does not work correctly for elements with `display: %value%`
The [slide](/docs/svelte/svelte-transition#slide) transition works by animating the `height` of the element, which requires a `display` style like `block`, `flex` or `grid`. It does not work for:
- `display: inline` (which is the default for elements like `<span>`), and its variants like `inline-block`, `inline-flex` and `inline-grid`
- `display: table` and `table-[name]`, which are the defaults for elements like `<table>` and `<tr>`
- `display: contents`

@ -32,7 +32,39 @@
## each_item_invalid_assignment
> Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)
> Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)
In legacy mode, it was possible to reassign or bind to the each block argument itself:
```svelte
<script>
let array = [1, 2, 3];
</script>
{#each array as entry}
<!-- reassignment -->
<button on:click={() => entry = 4}>change</button>
<!-- binding -->
<input bind:value={entry}>
{/each}
```
This turned out to be buggy and unpredictable, particularly when working with derived values (such as `array.map(...)`), and as such is forbidden in runes mode. You can achieve the same outcome by using the index instead:
```svelte
<script>
let array = $state([1, 2, 3]);
</script>
{#each array as entry, i}
<!-- reassignment -->
<button onclick={() => array[i] = 4}>change</button>
<!-- binding -->
<input bind:value={array[i]}>
{/each}
```
## effect_invalid_placement
@ -54,6 +86,14 @@
> Imports of `svelte/internal/*` are forbidden. It contains private runtime code which is subject to change without notice. If you're importing from `svelte/internal/*` to work around a limitation of Svelte, please open an issue at https://github.com/sveltejs/svelte and explain your use case
## inspect_trace_generator
> `$inspect.trace(...)` cannot be used inside a generator function
## inspect_trace_invalid_placement
> `$inspect.trace(...)` must be the first statement of a function body
## invalid_arguments_usage
> The arguments keyword cannot be used within the template or at the top level of a component
@ -80,7 +120,11 @@
## props_duplicate
> Cannot use `$props()` more than once
> Cannot use `%rune%()` more than once
## props_id_invalid_placement
> `$props.id()` can only be used at the top level of components as a variable declaration initializer
## props_illegal_name
@ -118,6 +162,10 @@
> `%name%` is not a valid rune
## rune_invalid_spread
> `%rune%` cannot be called with a spread argument
## rune_invalid_usage
> Cannot use `%rune%` rune in non-runes mode

@ -50,9 +50,17 @@
> Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression
## bind_group_invalid_expression
> `bind:group` can only bind to an Identifier or MemberExpression
## bind_group_invalid_snippet_parameter
> Cannot `bind:group` to a snippet parameter
## bind_invalid_expression
> Can only bind to an Identifier or MemberExpression
> Can only bind to an Identifier or MemberExpression or a `{get, set}` pair
## bind_invalid_name
@ -60,6 +68,10 @@
> `bind:%name%` is not a valid binding. %explanation%
## bind_invalid_parens
> `bind:%name%={get, set}` must not have surrounding parentheses
## bind_invalid_target
> `bind:%name%` can only be used with %elements%
@ -110,7 +122,7 @@
## const_tag_invalid_placement
> `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>` or `<Component>`
> `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>`, `<svelte:boundary` or `<Component>`
## debug_tag_invalid_arguments
@ -410,6 +422,10 @@ See https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-ele
> '%word%' is a reserved word in JavaScript and cannot be used here
## unterminated_string_constant
> Unterminated string constant
## void_element_invalid_content
> Void elements cannot have children or closing tags

@ -49,7 +49,7 @@ Enforce that `autofocus` is not used on elements. Autofocusing elements can caus
## a11y_click_events_have_key_events
> Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type="button">` or `<a>` might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details
> Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type="button">` or `<a>` might be more appropriate
Enforce that visible, non-interactive elements with an `onclick` event are accompanied by a keyboard event handler.

@ -6,10 +6,36 @@
> Svelte 5 components are no longer classes. Instantiate them using `mount` or `hydrate` (imported from 'svelte') instead.
See the [migration guide](v5-migration-guide#Components-are-no-longer-classes) for more info.
## non_reactive_update
> `%name%` is updated, but is not declared with `$state(...)`. Changing its value will not correctly trigger updates
This warning is thrown when the compiler detects the following:
- a variable was declared without `$state` or `$state.raw`
- the variable is reassigned
- the variable is read in a reactive context
In this case, changing the value will not correctly trigger updates. Example:
```svelte
<script>
let reactive = $state('reactive');
let stale = 'stale';
</script>
<p>This value updates: {reactive}</p>
<p>This value does not update: {stale}</p>
<button onclick={() => {
stale = 'updated';
reactive = 'updated';
}}>update</button>
```
To fix this, wrap your variable declaration with `$state`.
## perf_avoid_inline_class
> Avoid 'new class' — instead, declare the class at the top level scope
@ -28,7 +54,73 @@
## state_referenced_locally
> State referenced in its own scope will never update. Did you mean to reference it inside a closure?
> This reference only captures the initial value of `%name%`. Did you mean to reference it inside a %type% instead?
This warning is thrown when the compiler detects the following:
- A reactive variable is declared
- ...and later reassigned...
- ...and referenced in the same scope
This 'breaks the link' to the original state declaration. For example, if you pass the state to a function, the function loses access to the state once it is reassigned:
```svelte
<!--- file: Parent.svelte --->
<script>
import { setContext } from 'svelte';
let count = $state(0);
// warning: state_referenced_locally
setContext('count', count);
</script>
<button onclick={() => count++}>
increment
</button>
```
```svelte
<!--- file: Child.svelte --->
<script>
import { getContext } from 'svelte';
const count = getContext('count');
</script>
<!-- This will never update -->
<p>The count is {count}</p>
```
To fix this, reference the variable such that it is lazily evaluated. For the above example, this can be achieved by wrapping `count` in a function:
```svelte
<!--- file: Parent.svelte --->
<script>
import { setContext } from 'svelte';
let count = $state(0);
setContext('count', +++() => count+++);
</script>
<button onclick={() => count++}>
increment
</button>
```
```svelte
<!--- file: Child.svelte --->
<script>
import { getContext } from 'svelte';
const count = getContext('count');
</script>
<!-- This will update -->
<p>The count is {+++count()+++}</p>
```
For more info, see [Passing state into functions]($state#Passing-state-into-functions).
## store_rune_conflict

@ -1,3 +1,17 @@
## css_unused_selector
> Unused CSS selector "%name%"
Svelte traverses both the template and the `<style>` tag to find out which of the CSS selectors are not used within the template, so it can remove them.
In some situations a selector may target an element that is not 'visible' to the compiler, for example because it is part of an `{@html ...}` tag or you're overriding styles in a child component. In these cases, use [`:global`](/docs/svelte/global-styles) to preserve the selector as-is:
```svelte
<div class="post">{@html content}</div>
<style>
.post :global {
p {...}
}
</style>
```

@ -34,10 +34,38 @@
> Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...></%name%>` rather than `<%name% ... />`
In HTML, there's [no such thing as a self-closing tag](https://jakearchibald.com/2023/against-self-closing-tags-in-html/). While this _looks_ like a self-contained element with some text next to it...
```html
<div>
<span class="icon" /> some text!
</div>
```
...a spec-compliant HTML parser (such as a browser) will in fact parse it like this, with the text _inside_ the icon:
```html
<div>
<span class="icon"> some text! </span>
</div>
```
Some templating languages (including Svelte) will 'fix' HTML by turning `<span />` into `<span></span>`. Others adhere to the spec. Both result in ambiguity and confusion when copy-pasting code between different contexts, and as such Svelte prompts you to resolve the ambiguity directly by having an explicit closing tag.
To automate this, run the dedicated migration:
```bash
npx sv migrate self-closing-tags
```
In a future version of Svelte, self-closing tags may be upgraded from a warning to an error.
## event_directive_deprecated
> Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead
See [the migration guide](v5-migration-guide#Event-changes) for more info.
## node_invalid_placement_ssr
> %message%. When rendering this component on the server, the resulting HTML will be modified by the browser (by moving, removing, or inserting elements), likely resulting in a `hydration_mismatch` warning
@ -54,6 +82,12 @@ This code will work when the component is rendered on the client (which is why t
> `context="module"` is deprecated, use the `module` attribute instead
```svelte
<script ---context="module"--- +++module+++>
let foo = 'bar';
</script>
```
## script_unknown_attribute
> Unrecognized attribute — should be one of `generics`, `lang` or `module`. If this exists for a preprocessor, ensure that the preprocessor removes it
@ -62,6 +96,8 @@ This code will work when the component is rendered on the client (which is why t
> Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead
See [the migration guide](v5-migration-guide#Snippets-instead-of-slots) for more info.
## svelte_component_deprecated
> `<svelte:component>` is deprecated in runes mode — components are dynamic by default
@ -104,3 +140,5 @@ A derived value may be used in other contexts:
## svelte_self_deprecated
> `<svelte:self>` is deprecated — use self-imports (e.g. `import %name% from './%basename%'`) instead
See [the note in the docs](legacy-svelte-self) for more info.

@ -1,3 +1,5 @@
## lifecycle_function_unavailable
> `%name%(...)` is not available on the server
Certain methods such as `mount` cannot be invoked while running in a server context. Avoid calling them eagerly, i.e. not during render.

@ -2,10 +2,56 @@
> Cannot use `{@render children(...)}` if the parent component uses `let:` directives. Consider using a named snippet instead
This error would be thrown in a setup like this:
```svelte
<!--- file: Parent.svelte --->
<List {items} let:entry>
<span>{entry}</span>
</List>
```
```svelte
<!--- file: List.svelte --->
<script>
let { items, children } = $props();
</script>
<ul>
{#each items as item}
<li>{@render children(item)}</li>
{/each}
</ul>
```
Here, `List.svelte` is using `{@render children(item)` which means it expects `Parent.svelte` to use snippets. Instead, `Parent.svelte` uses the deprecated `let:` directive. This combination of APIs is incompatible, hence the error.
## invalid_snippet_arguments
> A snippet function was passed invalid arguments. Snippets should only be instantiated via `{@render ...}`
## lifecycle_outside_component
> `%name%(...)` can only be used during component initialisation
Certain lifecycle methods can only be used during component initialisation. To fix this, make sure you're invoking the method inside the _top level of the instance script_ of your component.
```svelte
<script>
import { onMount } from 'svelte';
function handleClick() {
// This is wrong
onMount(() => {})
}
// This is correct
onMount(() => {})
</script>
<button onclick={handleClick}>click me</button>
```
## store_invalid_shape
> `%name%` is not a store with a `subscribe` method

@ -2,6 +2,8 @@
> `<svelte:element this="%tag%">` is a void element — it cannot have content
Elements such as `<input>` cannot have content, any children passed to these elements will be ignored.
## state_snapshot_uncloneable
> Value cannot be cloned with `$state.snapshot` — the original value was returned
@ -9,3 +11,10 @@
> The following properties cannot be cloned with `$state.snapshot` — the return value contains the originals:
>
> %properties%
`$state.snapshot` tries to clone the given value in order to return a reference that no longer changes. Certain objects may not be cloneable, in which case the original value is returned. In the following example, `property` is cloned, but `window` is not, because DOM elements are uncloneable:
```js
const object = $state({ property: 'this is cloneable', window })
const snapshot = $state.snapshot(object);
```

@ -2,18 +2,19 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.8.0",
"version": "5.26.1",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
"node": ">=18"
},
"files": [
"*.d.ts",
"src",
"!src/**/*.test.*",
"!src/**/*.d.ts",
"types",
"compiler",
"*.d.ts",
"README.md"
],
"module": "src/index-client.js",
@ -21,6 +22,7 @@
"exports": {
".": {
"types": "./types/index.d.ts",
"worker": "./src/index-server.js",
"browser": "./src/index-client.js",
"default": "./src/index-server.js"
},
@ -56,11 +58,15 @@
"./internal/flags/legacy": {
"default": "./src/internal/flags/legacy.js"
},
"./internal/flags/tracing": {
"default": "./src/internal/flags/tracing.js"
},
"./internal/server": {
"default": "./src/internal/server/index.js"
},
"./legacy": {
"types": "./types/index.d.ts",
"worker": "./src/legacy/legacy-server.js",
"browser": "./src/legacy/legacy-client.js",
"default": "./src/legacy/legacy-server.js"
},
@ -70,15 +76,21 @@
},
"./reactivity": {
"types": "./types/index.d.ts",
"worker": "./src/reactivity/index-server.js",
"browser": "./src/reactivity/index-client.js",
"default": "./src/reactivity/index-server.js"
},
"./reactivity/window": {
"types": "./types/index.d.ts",
"default": "./src/reactivity/window/index.js"
},
"./server": {
"types": "./types/index.d.ts",
"default": "./src/server/index.js"
},
"./store": {
"types": "./types/index.d.ts",
"worker": "./src/store/index-server.js",
"browser": "./src/store/index-client.js",
"default": "./src/store/index-server.js"
},
@ -126,24 +138,25 @@
"@rollup/plugin-virtual": "^3.0.2",
"@types/aria-query": "^5.0.4",
"@types/node": "^20.11.5",
"dts-buddy": "^0.5.3",
"dts-buddy": "^0.5.5",
"esbuild": "^0.21.5",
"rollup": "^4.22.4",
"source-map": "^0.7.4",
"tiny-glob": "^0.2.9",
"tinyglobby": "^0.2.12",
"typescript": "^5.5.4",
"vitest": "^2.0.5"
"vitest": "^2.1.9"
},
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@types/estree": "^1.0.5",
"acorn": "^8.12.1",
"acorn-typescript": "^1.4.13",
"@sveltejs/acorn-typescript": "^1.0.5",
"aria-query": "^5.3.1",
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
"esm-env": "^1.2.1",
"esrap": "^1.2.3",
"esrap": "^1.4.6",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",

@ -58,6 +58,7 @@ for (const key in pkg.exports) {
if (key === './internal') continue;
if (key === './internal/disclose-version') continue;
if (key === './internal/flags/legacy') continue;
if (key === './internal/flags/tracing') continue;
for (const type of ['browser', 'default']) {
if (!pkg.exports[key][type]) continue;
@ -91,6 +92,7 @@ const bundle = await bundle_code(
</script>
<svelte:head><title>hi</title></svelte:head>
<input bind:value={foo} />
<a href={foo} class={foo}>a</a>
<a {...foo}>a</a>
@ -134,6 +136,15 @@ if (!bundle.includes('component_context.l')) {
console.error(`❌ Legacy code not treeshakeable`);
}
if (!bundle.includes(`'CreatedAt'`)) {
// eslint-disable-next-line no-console
console.error(`$inspect.trace code treeshakeable`);
} else {
failed = true;
// eslint-disable-next-line no-console
console.error(`$inspect.trace code not treeshakeable`);
}
if (failed) {
// eslint-disable-next-line no-console
console.error(bundle);

@ -35,6 +35,7 @@ await createBundle({
[`${pkg.name}/legacy`]: `${dir}/src/legacy/legacy-client.js`,
[`${pkg.name}/motion`]: `${dir}/src/motion/public.d.ts`,
[`${pkg.name}/reactivity`]: `${dir}/src/reactivity/index-client.js`,
[`${pkg.name}/reactivity/window`]: `${dir}/src/reactivity/window/index.js`,
[`${pkg.name}/server`]: `${dir}/src/server/index.d.ts`,
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,
[`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`,
@ -45,6 +46,8 @@ await createBundle({
}
});
fs.appendFileSync(`${dir}/types/index.d.ts`, '\n');
const types = fs.readFileSync(`${dir}/types/index.d.ts`, 'utf-8');
const bad_links = [...types.matchAll(/\]\((\/[^)]+)\)/g)];

@ -8,8 +8,6 @@ fs.writeFileSync(
/**
* The current version, as set in package.json.
*
* https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string}
*/
export const VERSION = '${pkg.version}';

@ -339,7 +339,7 @@ function transform(name, dest) {
};
for (let i = 0; i < node.expressions.length; i += 1) {
const q = node.quasis[i + 1];
const q = structuredClone(node.quasis[i + 1]);
const e = node.expressions[i];
if (e.type === 'Literal' && e.value === 'CODE') {
@ -355,10 +355,12 @@ function transform(name, dest) {
}
if (message.type === 'TemplateLiteral') {
quasi.value.raw += message.quasis[0].value.raw + q.value.raw;
out.quasis.push(...message.quasis.slice(1));
out.expressions.push(...message.expressions);
quasi = message.quasis[message.quasis.length - 1];
const m = structuredClone(message);
quasi.value.raw += m.quasis[0].value.raw;
out.quasis.push(...m.quasis.slice(1));
out.expressions.push(...m.expressions);
quasi = m.quasis[m.quasis.length - 1];
quasi.value.raw += q.value.raw;
continue;
}
}
@ -387,7 +389,6 @@ function transform(name, dest) {
ast.body.push(clone);
}
// @ts-expect-error
const module = esrap.print(ast);
fs.writeFileSync(

@ -7,11 +7,10 @@ import { DEV } from 'esm-env';
*/
export function CODE(PARAMETER) {
if (DEV) {
const error = new Error(`${'CODE'}\n${MESSAGE}`);
const error = new Error(`${'CODE'}\n${MESSAGE}\nhttps://svelte.dev/e/${'CODE'}`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error('CODE');
throw new Error(`https://svelte.dev/e/${'CODE'}`);
}
}

@ -9,9 +9,12 @@ var normal = 'font-weight: normal';
*/
export function CODE(PARAMETER) {
if (DEV) {
console.warn(`%c[svelte] ${'CODE'}\n%c${MESSAGE}`, bold, normal);
console.warn(
`%c[svelte] ${'CODE'}\n%c${MESSAGE}\nhttps://svelte.dev/e/${'CODE'}`,
bold,
normal
);
} else {
// TODO print a link to the documentation
console.warn('CODE');
console.warn(`https://svelte.dev/e/${'CODE'}`);
}
}

@ -56,5 +56,5 @@ function e(node, code, message) {
* @returns {never}
*/
export function CODE(node, PARAMETER) {
e(node, 'CODE', MESSAGE);
e(node, 'CODE', `${MESSAGE}\nhttps://svelte.dev/e/${'CODE'}`);
}

@ -47,5 +47,5 @@ export const codes = CODES;
* @param {string} PARAMETER
*/
export function CODE(node, PARAMETER) {
w(node, 'CODE', MESSAGE);
w(node, 'CODE', `${MESSAGE}\nhttps://svelte.dev/e/${'CODE'}`);
}

@ -4,7 +4,7 @@
* @returns {never}
*/
export function CODE(PARAMETER) {
const error = new Error(`${'CODE'}\n${MESSAGE}`);
const error = new Error(`${'CODE'}\n${MESSAGE}\nhttps://svelte.dev/e/${'CODE'}`);
error.name = 'Svelte error';
throw error;
}

@ -7,11 +7,10 @@ import { DEV } from 'esm-env';
*/
export function CODE(PARAMETER) {
if (DEV) {
const error = new Error(`${'CODE'}\n${MESSAGE}`);
const error = new Error(`${'CODE'}\n${MESSAGE}\nhttps://svelte.dev/e/${'CODE'}`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error('CODE');
throw new Error(`https://svelte.dev/e/${'CODE'}`);
}
}

@ -9,9 +9,12 @@ var normal = 'font-weight: normal';
*/
export function CODE(PARAMETER) {
if (DEV) {
console.warn(`%c[svelte] ${'CODE'}\n%c${MESSAGE}`, bold, normal);
console.warn(
`%c[svelte] ${'CODE'}\n%c${MESSAGE}\nhttps://svelte.dev/e/${'CODE'}`,
bold,
normal
);
} else {
// TODO print a link to the documentation
console.warn('CODE');
console.warn(`https://svelte.dev/e/${'CODE'}`);
}
}

@ -338,6 +338,38 @@ declare namespace $effect {
*/
declare function $props(): any;
declare namespace $props {
/**
* Generates an ID that is unique to the current component instance. When hydrating a server-rendered component,
* the value will be consistent between server and client.
*
* This is useful for linking elements via attributes like `for` and `aria-labelledby`.
* @since 5.20.0
*/
export function id(): string;
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}
/**
* Declares a prop as bindable, meaning the parent component can use `bind:propName={value}` to bind to it.
*
@ -349,6 +381,29 @@ declare function $props(): any;
*/
declare function $bindable<T>(fallback?: T): T;
declare namespace $bindable {
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}
/**
* Inspects one or more values whenever they, or the properties they contain, change. Example:
*
@ -371,6 +426,46 @@ declare function $inspect<T extends any[]>(
...values: T
): { with: (fn: (type: 'init' | 'update', ...values: T) => void) => void };
declare namespace $inspect {
/**
* Tracks which reactive state changes caused an effect to re-run. Must be the first
* statement of a function body. Example:
*
* ```svelte
* <script>
* let count = $state(0);
*
* $effect(() => {
* $inspect.trace('my effect');
*
* count;
* });
* </script>
*/
export function trace(name?: string): void;
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}
/**
* Retrieves the `this` reference of the custom element that contains this component. Example:
*
@ -391,3 +486,26 @@ declare function $inspect<T extends any[]>(
* https://svelte.dev/docs/svelte/$host
*/
declare function $host<El extends HTMLElement = HTMLElement>(): El;
declare namespace $host {
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}

@ -11,18 +11,37 @@ import { cubicOut } from '../easing/index.js';
* @returns {AnimationConfig}
*/
export function flip(node, { from, to }, params = {}) {
var { delay = 0, duration = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
var style = getComputedStyle(node);
var zoom = get_zoom(node); // https://drafts.csswg.org/css-viewport/#effective-zoom
// find the transform origin, expressed as a pair of values between 0 and 1
var transform = style.transform === 'none' ? '' : style.transform;
var [ox, oy] = style.transformOrigin.split(' ').map(parseFloat);
ox /= node.clientWidth;
oy /= node.clientHeight;
// calculate effect of parent transforms and zoom
var zoom = get_zoom(node); // https://drafts.csswg.org/css-viewport/#effective-zoom
var sx = node.clientWidth / to.width / zoom;
var sy = node.clientHeight / to.height / zoom;
// find the starting position of the transform origin
var fx = from.left + from.width * ox;
var fy = from.top + from.height * oy;
// find the ending position of the transform origin
var tx = to.left + to.width * ox;
var ty = to.top + to.height * oy;
// find the translation at the start of the transform
var dx = (fx - tx) * sx;
var dy = (fy - ty) * sy;
// find the relative scale at the start of the transform
var dsx = from.width / to.width;
var dsy = from.height / to.height;
var dx = (from.left + dsx * ox - (to.left + ox)) / zoom;
var dy = (from.top + dsy * oy - (to.top + oy)) / zoom;
var { delay = 0, duration = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
return {
delay,
duration: typeof duration === 'function' ? duration(Math.sqrt(dx * dx + dy * dy)) : duration,
@ -32,7 +51,8 @@ export function flip(node, { from, to }, params = {}) {
var y = u * dy;
var sx = t + u * dsx;
var sy = t + u * dsy;
return `transform: ${transform} scale(${sx}, ${sy}) translate(${x}px, ${y}px);`;
return `transform: ${transform} translate(${x}px, ${y}px) scale(${sx}, ${sy});`;
}
};
}

File diff suppressed because it is too large Load Diff

@ -76,7 +76,7 @@ export function compileModule(source, options) {
*
* @overload
* @param {string} source
* @param {{ filename?: string; modern: true }} options
* @param {{ filename?: string; modern: true; loose?: boolean }} options
* @returns {AST.Root}
*/
@ -88,7 +88,7 @@ export function compileModule(source, options) {
*
* @overload
* @param {string} source
* @param {{ filename?: string; modern?: false }} [options]
* @param {{ filename?: string; modern?: false; loose?: boolean }} [options]
* @returns {Record<string, any>}
*/
@ -98,16 +98,18 @@ export function compileModule(source, options) {
* The `modern` option (`false` by default in Svelte 5) makes the parser return a modern AST instead of the legacy AST.
* `modern` will become `true` by default in Svelte 6, and the option will be removed in Svelte 7.
*
* The `loose` option, available since 5.13.0, tries to always return an AST even if the input will not successfully compile.
*
* @param {string} source
* @param {{ filename?: string; rootDir?: string; modern?: boolean }} [options]
* @param {{ filename?: string; rootDir?: string; modern?: boolean; loose?: boolean }} [options]
* @returns {AST.Root | LegacyRoot}
*/
export function parse(source, { filename, rootDir, modern } = {}) {
export function parse(source, { filename, rootDir, modern, loose } = {}) {
source = remove_bom(source);
state.reset_warning_filter(() => false);
state.reset(source, { filename: filename ?? '(unknown)', rootDir });
const ast = _parse(source);
const ast = _parse(source, loose);
return to_public_ast(source, ast, modern);
}

@ -1,5 +1,5 @@
/** @import { Expression } from 'estree' */
/** @import { AST, SvelteNode, TemplateNode } from '#compiler' */
/** @import { AST } from '#compiler' */
/** @import * as Legacy from './types/legacy-nodes.js' */
import { walk } from 'zimmerframe';
import {
@ -11,7 +11,7 @@ import { extract_svelte_ignore } from './utils/extract_svelte_ignore.js';
/**
* Some of the legacy Svelte AST nodes remove whitespace from the start and end of their children.
* @param {TemplateNode[]} nodes
* @param {AST.TemplateNode[]} nodes
*/
function remove_surrounding_whitespace_nodes(nodes) {
const first = nodes.at(0);
@ -40,7 +40,7 @@ function remove_surrounding_whitespace_nodes(nodes) {
* @returns {Legacy.LegacyRoot}
*/
export function convert(source, ast) {
const root = /** @type {SvelteNode | Legacy.LegacySvelteNode} */ (ast);
const root = /** @type {AST.SvelteNode | Legacy.LegacySvelteNode} */ (ast);
return /** @type {Legacy.LegacyRoot} */ (
walk(root, null, {

@ -2,7 +2,7 @@
/** @import { Visitors } from 'zimmerframe' */
/** @import { ComponentAnalysis } from '../phases/types.js' */
/** @import { Scope, ScopeRoot } from '../phases/scope.js' */
/** @import { AST, Binding, SvelteNode, ValidatedCompileOptions } from '#compiler' */
/** @import { AST, Binding, ValidatedCompileOptions } from '#compiler' */
import MagicString from 'magic-string';
import { walk } from 'zimmerframe';
import { parse } from '../phases/1-parse/index.js';
@ -479,7 +479,7 @@ export function migrate(source, { filename, use_ts } = {}) {
* }} State
*/
/** @type {Visitors<SvelteNode, State>} */
/** @type {Visitors<AST.SvelteNode, State>} */
const instance_script = {
_(node, { state, next }) {
// @ts-expect-error
@ -944,54 +944,53 @@ const instance_script = {
node.body.type === 'ExpressionStatement' &&
node.body.expression.type === 'AssignmentExpression'
) {
const ids = extract_identifiers(node.body.expression.left);
const [, expression_ids] = extract_all_identifiers_from_expression(
node.body.expression.right
);
const bindings = ids.map((id) => state.scope.get(id.name));
const reassigned_bindings = bindings.filter((b) => b?.reassigned);
const { left, right } = node.body.expression;
if (
reassigned_bindings.length === 0 &&
!bindings.some((b) => b?.kind === 'store_sub') &&
node.body.expression.left.type !== 'MemberExpression'
) {
let { start, end } = /** @type {{ start: number, end: number }} */ (
node.body.expression.right
);
const ids = extract_identifiers(left);
const [, expression_ids] = extract_all_identifiers_from_expression(right);
const bindings = ids.map((id) => /** @type {Binding} */ (state.scope.get(id.name)));
check_rune_binding('derived');
if (bindings.every((b) => b.kind === 'legacy_reactive')) {
if (
right.type !== 'Literal' &&
bindings.every((b) => b.kind !== 'store_sub') &&
left.type !== 'MemberExpression'
) {
let { start, end } = /** @type {{ start: number, end: number }} */ (right);
// $derived
state.str.update(
/** @type {number} */ (node.start),
/** @type {number} */ (node.body.expression.start),
'let '
);
check_rune_binding('derived');
if (node.body.expression.right.type === 'SequenceExpression') {
while (state.str.original[start] !== '(') start -= 1;
while (state.str.original[end - 1] !== ')') end += 1;
}
// $derived
state.str.update(
/** @type {number} */ (node.start),
/** @type {number} */ (node.body.expression.start),
'let '
);
state.str.prependRight(start, `$derived(`);
if (right.type === 'SequenceExpression') {
while (state.str.original[start] !== '(') start -= 1;
while (state.str.original[end - 1] !== ')') end += 1;
}
state.str.prependRight(start, `$derived(`);
// in a case like `$: ({ a } = b())`, there's already a trailing parenthesis.
// otherwise, we need to add one
if (state.str.original[/** @type {number} */ (node.body.start)] !== '(') {
state.str.appendLeft(end, `)`);
}
// in a case like `$: ({ a } = b())`, there's already a trailing parenthesis.
// otherwise, we need to add one
if (state.str.original[/** @type {number} */ (node.body.start)] !== '(') {
state.str.appendLeft(end, `)`);
return;
}
return;
} else {
for (const binding of reassigned_bindings) {
if (binding && (ids.includes(binding.node) || expression_ids.length === 0)) {
for (const binding of bindings) {
if (binding.reassigned && (ids.includes(binding.node) || expression_ids.length === 0)) {
check_rune_binding('state');
const init =
binding.kind === 'state'
? ' = $state()'
: expression_ids.length === 0
? ` = $state(${state.str.original.substring(/** @type {number} */ (node.body.expression.right.start), node.body.expression.right.end)})`
? ` = $state(${state.str.original.substring(/** @type {number} */ (right.start), right.end)})`
: '';
// implicitly-declared variable which we need to make explicit
state.str.prependLeft(
@ -1000,7 +999,8 @@ const instance_script = {
);
}
}
if (expression_ids.length === 0 && !bindings.some((b) => b?.kind === 'store_sub')) {
if (expression_ids.length === 0 && bindings.every((b) => b.kind !== 'store_sub')) {
state.str.remove(/** @type {number} */ (node.start), /** @type {number} */ (node.end));
return;
}
@ -1050,7 +1050,7 @@ function trim_block(state, start, end) {
}
}
/** @type {Visitors<SvelteNode, State>} */
/** @type {Visitors<AST.SvelteNode, State>} */
const template = {
Identifier(node, { state, path }) {
handle_identifier(node, state, path);
@ -1410,7 +1410,7 @@ const template = {
/**
* @param {AST.RegularElement | AST.SvelteElement | AST.SvelteComponent | AST.Component | AST.SlotElement | AST.SvelteFragment} node
* @param {SvelteNode[]} path
* @param {AST.SvelteNode[]} path
* @param {State} state
*/
function migrate_slot_usage(node, path, state) {
@ -1580,7 +1580,7 @@ function migrate_slot_usage(node, path, state) {
/**
* @param {VariableDeclarator} declarator
* @param {State} state
* @param {SvelteNode[]} path
* @param {AST.SvelteNode[]} path
*/
function extract_type_and_comment(declarator, state, path) {
const str = state.str;
@ -1592,7 +1592,6 @@ function extract_type_and_comment(declarator, state, path) {
const comment_start = /** @type {any} */ (comment_node)?.start;
const comment_end = /** @type {any} */ (comment_node)?.end;
let comment = comment_node && str.original.substring(comment_start, comment_end);
if (comment_node) {
str.update(comment_start, comment_end, '');
}
@ -1673,6 +1672,11 @@ function extract_type_and_comment(declarator, state, path) {
state.has_type_or_fallback = true;
const match = /@type {(.+)}/.exec(comment_node.value);
if (match) {
// try to find JSDoc comments after a hyphen `-`
const jsdoc_comment = /@type {.+} (?:\w+|\[.*?\]) - (.+)/.exec(comment_node.value);
if (jsdoc_comment) {
cleaned_comment += jsdoc_comment[1]?.trim();
}
return {
type: match[1],
comment: cleaned_comment,
@ -1693,7 +1697,6 @@ function extract_type_and_comment(declarator, state, path) {
};
}
}
return {
type: 'any',
comment: state.uses_ts ? comment : cleaned_comment,

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save