`), and cannot be observed with a `ResizeObserver`. You will need to change the `display` style of these elements to something else, such as `inline-block`. Note that CSS transformations do not trigger `ResizeObserver` callbacks.
## bind:this
```
bind:this={dom_node}
```
To get a reference to a DOM node, use `bind:this`. The value will be `undefined` until the component is mounted — in other words, you should read it inside an effect or an event handler, but not during component initialisation:
```
```
Components also support `bind:this`, allowing you to interact with component instances programmatically.
```
cart.empty()}> Empty shopping cart
```
```
```
> \[!NOTE\] In case of using [the function bindings](#Function-bindings), the getter is required to ensure that the correct value is nullified on component or element destruction.
## bind:_property_ for components
```
bind:property={variable}
```
You can bind to component props using the same syntax as for elements.
```
```
While Svelte props are reactive without binding, that reactivity only flows downward into the component by default. Using `bind:property` allows changes to the property from within the component to flow back up out of the component.
To mark a property as bindable, use the [`$bindable`]($bindable) rune:
```
```
Declaring a property as bindable means it _can_ be used using `bind:`, not that it _must_ be used using `bind:`.
Bindable properties can have a fallback value:
```
```
This fallback value _only_ applies when the property is _not_ bound. When the property is bound and a fallback value is present, the parent is expected to provide a value other than `undefined`, else a runtime error is thrown. This prevents hard-to-reason-about situations where it's unclear which value should apply.
## [use:](https://docharvest.github.io/docs/svelte/03-template-syntax/13-use/)
Contents
svelte
use:
Svelte
use:
> \[!NOTE\] In Svelte 5.29 and newer, consider using [attachments](@attach) instead, as they are more flexible and composable.
Actions are functions that are called when an element is mounted. They are added with the `use:` directive, and will typically use an `$effect` so that they can reset any state when the element is unmounted:
```
...
```
An action can be called with an argument:
```
...
```
The action is only called once (but not during server-side rendering) — it will _not_ run again if the argument changes.
> \[!LEGACY\] Prior to the `$effect` rune, actions could return an object with `update` and `destroy` methods, where `update` would be called with the latest value of the argument if it changed. Using effects is preferred.
## Typing
The `Action` interface receives three optional type arguments — a node type (which can be `Element`, if the action applies to everything), a parameter, and any custom event handlers created by the action:
```
...
```
## [transition:](https://docharvest.github.io/docs/svelte/03-template-syntax/14-transition/)
Contents
svelte
transition:
Svelte
transition:
A _transition_ is triggered by an element entering or leaving the DOM as a result of a state change.
When a block (such as an `{#if ...}` block) is transitioning out, all elements inside it, including those that do not have their own transitions, are kept in the DOM until every transition in the block has been completed.
The `transition:` directive indicates a _bidirectional_ transition, which means it can be smoothly reversed while the transition is in progress.
```
visible = !visible}>toggle
{#if visible}
fades in and out
{/if}
```
## 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.
```
{#if x}
{#if y}
fades in and out only when y changes
fades in and out when x or y change
{/if}
{/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.
(The double `{{curlies}}` aren't a special syntax; this is an object literal inside an expression tag.)
```
{#if visible}
fades in and out over two seconds
{/if}
```
## Custom transition functions
```
/// copy: false
// @noErrors
transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
```
Transitions can use custom functions. If the returned object has a `css` function, Svelte will generate keyframes for a [web animation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
The `t` argument passed to `css` is a value between `0` and `1` after the `easing` function has been applied. _In_ transitions run from `0` to `1`, _out_ transitions run from `1` to `0` — in other words, `1` is the element's natural state, as though no transition had been applied. The `u` argument is equal to `1 - t`.
The function is called repeatedly _before_ the transition begins, with different `t` and `u` arguments.
```
{#if visible}
whooshes in
{/if}
```
A custom transition function can also return a `tick` function, which is called _during_ the transition with the same `t` and `u` arguments.
> \[!NOTE\] If it's possible to use `css` instead of `tick`, do so — web animations can run off the main thread, preventing jank on slower devices.
```
{#if visible}
The quick brown fox jumps over the lazy dog
{/if}
```
If a transition returns a function instead of a transition object, the function will be called in the next microtask. This allows multiple transitions to coordinate, making [crossfade effects](/tutorial/deferred-transitions) possible.
Transition functions also receive a third argument, `options`, which contains information about the transition.
Available values in the `options` object are:
- `direction` - one of `in`, `out`, or `both` depending on the type of transition
## Transition events
An element with transitions will dispatch the following events in addition to any standard DOM events:
- `introstart`
- `introend`
- `outrostart`
- `outroend`
```
{#if visible}
(status = 'intro started')}
onoutrostart={() => (status = 'outro started')}
onintroend={() => (status = 'intro ended')}
onoutroend={() => (status = 'outro ended')}
>
Flies in and out
{/if}
```
## [in: and out:](https://docharvest.github.io/docs/svelte/03-template-syntax/15-in-and-out/)
Contents
svelte
in: and out:
Svelte
in: and out:
The `in:` and `out:` directives are identical to [`transition:`](transition), except that the resulting transitions are not bidirectional — an `in` transition will continue to 'play' alongside the `out` transition, rather than reversing, if the block is outroed while the transition is in progress. If an out transition is aborted, transitions will restart from scratch.
```
visible
{#if visible}
flies in, fades out
{/if}
```
## [animate:](https://docharvest.github.io/docs/svelte/03-template-syntax/16-animate/)
Contents
svelte
animate:
Svelte
animate:
An animation is triggered when the contents of a [keyed each block](each#Keyed-each-blocks) are re-ordered. Animations do not run when an element is added or removed, only when the index of an existing data item within the each block changes. Animate directives must be on an element that is an _immediate_ child of a keyed each block.
Animations can be used with Svelte's [built-in animation functions](svelte-animate) or [custom animation functions](#Custom-animation-functions).
```
{#each list as item, index (item)}
{item}
{/each}
```
## Animation Parameters
As with actions and transitions, animations can have parameters.
(The double `{{curlies}}` aren't a special syntax; this is an object literal inside an expression tag.)
```
{#each list as item, index (item)}
{item}
{/each}
```
## Custom animation functions
```
/// copy: false
// @noErrors
animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
```
Animations can use custom functions that provide the `node`, an `animation` object and any `parameters` as arguments. The `animation` parameter is an object containing `from` and `to` properties each containing a [DOMRect](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect#Properties) describing the geometry of the element in its `start` and `end` positions. The `from` property is the DOMRect of the element in its starting position, and the `to` property is the DOMRect of the element in its final position after the list has been reordered and the DOM updated.
If the returned object has a `css` method, Svelte will create a [web animation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) that plays on the element.
The `t` argument passed to `css` is a value that goes from `0` and `1` after the `easing` function has been applied. The `u` argument is equal to `1 - t`.
The function is called repeatedly _before_ the animation begins, with different `t` and `u` arguments.
```
{#each list as item, index (item)}
{item}
{/each}
```
A custom animation function can also return a `tick` function, which is called _during_ the animation with the same `t` and `u` arguments.
> \[!NOTE\] If it's possible to use `css` instead of `tick`, do so — web animations can run off the main thread, preventing jank on slower devices.
```
{#each list as item, index (item)}
{item}
{/each}
```
## [style:](https://docharvest.github.io/docs/svelte/03-template-syntax/17-style/)
Contents
svelte
style:
Svelte
style:
The `style:` directive provides a shorthand for setting multiple styles on an element.
```
...
...
```
The value can contain arbitrary expressions:
```
...
```
The shorthand form is allowed:
```
...
```
Multiple styles can be set on a single element:
```
...
```
To mark a style as important, use the `|important` modifier:
```
...
```
When `style:` directives are combined with `style` attributes, the directives will take precedence, even over `!important` properties:
```
This will be red
This will still be red
```
You can set CSS custom properties:
```
...
```
## [class](https://docharvest.github.io/docs/svelte/03-template-syntax/18-class/)
Contents
svelte
class
Svelte
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:
```
...
```
> \[!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:
```
...
```
If the value is an array, the truthy values are combined:
```
...
```
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:
```
{@render props.children?.()}
```
The user of this component has the same flexibility to use a mixture of objects, arrays and strings:
```
useTailwind = true}
class={{ 'bg-blue-700 sm:w-1/2': useTailwind }}
>
Accept the inevitability of Tailwind
```
Since Svelte 5.19, 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:
```
...
```
## The `class:` directive
Prior to Svelte 5.16, the `class:` directive was the most convenient way to set classes on elements conditionally.
```
...
...
```
As with other directives, we can use a shorthand when the name of the class coincides with the value:
```
...
```
> \[!NOTE\] Unless you're using an older version of Svelte, consider avoiding `class:`, since the attribute is more powerful and composable.
## [await](https://docharvest.github.io/docs/svelte/03-template-syntax/19-await-expressions/)
Contents
svelte
await
Svelte
await
As of Svelte 5.36, you can use the `await` keyword inside your components in three places where it was previously unavailable:
- at the top level of your component's `
{a} + {b} = {await add(a, b)}
```
...if you increment `a`, the contents of the `` will _not_ immediately update to read this —
```
2 + 2 = 3
```
— instead, the text will update to `2 + 2 = 4` when `add(a, b)` resolves.
Updates can overlap — a fast update will be reflected in the UI while an earlier slow update is still ongoing.
## Concurrency
Svelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup...
```
{await one(x)}
{await two(y)}
```
...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential.
This does not apply to sequential `await` expressions inside your `
{
pending?.commit();
pending = null;
// in case `pending` didn't exist
// (if it did, this is a no-op)
open = true;
}}
>open menu
{#if open}
open = false} />
{/if}
```
## Caveats
As an experimental feature, the details of how `await` is handled (and related APIs like `$effect.pending()`) are subject to breaking changes outside of a semver major release, though we intend to keep such changes to a bare minimum.
## Breaking changes
Effects run in a slightly different order when the `experimental.async` option is `true`. Specifically, _block_ effects like `{#if ...}` and `{#each ...}` now run before an `$effect.pre` or `beforeUpdate` in the same component, which means that in [very rare situations](/playground/untitled?#H4sIAAAAAAAAE22R3VLDIBCFX2WLvUhnTHsf0zre-Q7WmfwtFV2BgU1rJ5N3F0jaOuoVcPbw7VkYhK4_URTiGYkMnIyjDjLsFGO3EvdCKkIvipdB8NlGXxSCPt96snbtj0gctab2-J_eGs2oOWBE6VunLO_2es-EDKZ5x5ZhC0vPNWM2gHXGouNzAex6hHH1cPHil_Lsb95YT9VQX6KUAbS2DrNsBdsdDFHe8_XSYjH1SrhELTe3MLpsemajweiWVPuxHSbKNd-8eQTdE0EBf4OOaSg2hwNhhE_ABB_ulJzjj9FULvIcqgm5vnAqUB7wWFMfhuugQWkcAr8hVD-mq8D12kOep24J_IszToOXdveGDsuNnZwbJUNlXsKnhJdhUcTo42s41YpOSneikDV5HL8BktM6yRcCAAA=) it is possible to update a block that should no longer exist, but only if you update state inside an effect, [which you should avoid]($effect#When-not-to-use-$effect).
## [Styling](https://docharvest.github.io/docs/svelte/04-styling/)
Contents
svelte
Styling
Svelte
Styling
## [Scoped styles](https://docharvest.github.io/docs/svelte/04-styling/01-scoped-styles/)
Contents
svelte
Scoped styles
Svelte
Scoped styles
Svelte components can include a `
```
## Specificity
Each scoped selector receives a [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) increase of 0-1-0, as a result of the scoping class (e.g. `.svelte-123xyz`) being added to the selector. This means that (for example) a `p` selector defined in a component will take precedence over a `p` selector defined in a global stylesheet, even if the global stylesheet is loaded later.
In some cases, the scoping class must be added to a selector multiple times, but after the first occurrence it is added with `:where(.svelte-xyz123)` in order to not increase specificity further.
## Scoped keyframes
If a component defines `@keyframes`, the name is scoped to the component using the same hashing approach. Any `animation` rules in the component will be similarly adjusted:
```
```
## [Global styles](https://docharvest.github.io/docs/svelte/04-styling/02-global-styles/)
Contents
svelte
Global styles
Svelte
Global styles
## :global(...)
To apply styles to a single selector globally, use the `:global(...)` modifier:
```
```
If you want to make @keyframes that are accessible globally, you need to prepend your keyframe names with `-global-`.
The `-global-` part will be removed when compiled, and the keyframe will then be referenced using just `my-animation-name` elsewhere in your code.
```
```
## :global
To apply styles to a group of selectors globally, create a `:global {...}` block:
```
```
> \[!NOTE\] The second example above could also be written as an equivalent `.a :global .b .c .d` selector, where everything after the `:global` is unscoped, though the nested form is preferred.
## [Custom properties](https://docharvest.github.io/docs/svelte/04-styling/03-custom-properties/)
Contents
svelte
Custom properties
Svelte
Custom properties
You can pass CSS custom properties — both static and dynamic — to components:
```
```
The above code essentially desugars to this:
```
```
For an SVG element, it would use `` instead:
```
```
Inside the component, we can read these custom properties (and provide fallback values) using [`var(...)`](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties):
```
```
You don't _have_ to specify the values directly on the component; as long as the custom properties are defined on a parent element, the component can use them. It's common to define custom properties on the `:root` element in a global stylesheet so that they apply to your entire application.
> \[!NOTE\] While the extra element will not affect layout, it _will_ affect any CSS selectors that (for example) use the `>` combinator to target an element directly inside the component's container.
## [Nested
```
If this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:
```
```
## Context
Consider using context instead of declaring state in a shared module. This will scope the state to the part of the app that needs it, and eliminate the possibility of it leaking between users when server-side rendering.
Use `createContext` rather than `setContext` and `getContext`, as it provides type safety.
## Async Svelte
If using version 5.36 or higher, you can use [await expressions](await-expressions) and [hydratable](hydratable) to use promises directly inside components. Note that these require the `experimental.async` option to be enabled in `svelte.config.js` as they are not yet considered fully stable.
## Avoid legacy features
Always use runes mode for new code, and avoid features that have more modern replacements:
- use `$state` instead of implicit reactivity (e.g. `let count = 0; count += 1`)
- use `$derived` and `$effect` instead of `$:` assignments and statements (but only use effects when there is no better solution)
- use `$props` instead of `export let`, `$$props` and `$$restProps`
- use `onclick={...}` instead of `on:click={...}`
- use `{#snippet ...}` and `{@render ...}` instead of `` and `$$slots` and ``
- use `` instead of ``
- use `import Self from './ThisComponent.svelte'` and `` instead of ``
- use classes with `$state` fields to share reactivity between components, instead of using stores
- use `{@attach ...}` instead of `use:action`
- use clsx-style arrays and objects in `class` attributes, instead of the `class:` directive
## [Testing](https://docharvest.github.io/docs/svelte/07-misc/02-testing/)
Contents
svelte
Testing
Svelte
Testing
Testing helps you write and maintain your code and guard against regressions. Testing frameworks help you with that, allowing you to describe assertions or expectations about how your code should behave. Svelte is unopinionated about which testing framework you use — you can write unit tests, integration tests, and end-to-end tests using solutions like [Vitest](https://vitest.dev/), [Jasmine](https://jasmine.github.io/), [Cypress](https://www.cypress.io/) and [Playwright](https://playwright.dev/).
## Unit and component tests with Vitest
Unit tests allow you to test small isolated parts of your code. Integration tests allow you to test parts of your application to see if they work together. If you're using Vite (including via SvelteKit), we recommend using [Vitest](https://vitest.dev/). You can use the Svelte CLI to [setup Vitest](/docs/cli/vitest) either during project creation or later on.
To setup Vitest manually, first install it:
```
npm install -D vitest
```
Then adjust your `vite.config.js`:
```
/// file: vite.config.js
import { defineConfig } from +++'vitest/config'+++;
export default defineConfig({
// ...
// Tell Vitest to use the `browser` entry points in `package.json` files, even though it's running in Node
resolve: process.env.VITEST
? {
conditions: ['browser']
}
: undefined
});
```
> \[!NOTE\] If loading the browser version of all your packages is undesirable, because (for example) you also test backend libraries, [you may need to resort to an alias configuration](https://github.com/testing-library/svelte-testing-library/issues/222#issuecomment-1909993331)
You can now write unit tests for code inside your `.js/.ts` files:
```
/// file: multiplier.svelte.test.js
// @filename: multiplier.svelte.ts
export function multiplier(initial: number, k: number) {
let count = $state(initial);
return {
get value() {
return count * k;
},
set: (c: number) => {
count = c;
}
};
}
// @filename: multiplier.svelte.test.js
// ---cut---
import { flushSync } from 'svelte';
import { expect, test } from 'vitest';
import { multiplier } from './multiplier.svelte.js';
test('Multiplier', () => {
let double = multiplier(0, 2);
expect(double.value).toEqual(0);
double.set(5);
expect(double.value).toEqual(10);
});
```
```
/// 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
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`:
```
/// file: multiplier.svelte.test.js
// @filename: multiplier.svelte.ts
export function multiplier(getCount: () => number, k: number) {
return {
get value() {
return getCount() * k;
}
};
}
// @filename: multiplier.svelte.test.js
// ---cut---
import { flushSync } from 'svelte';
import { expect, test } from 'vitest';
import { multiplier } from './multiplier.svelte.js';
test('Multiplier', () => {
let count = $state(0);
let double = multiplier(() => count, 2);
expect(double.value).toEqual(0);
count = 5;
expect(double.value).toEqual(10);
});
```
```
/// 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`:
```
/// file: logger.svelte.test.js
// @filename: logger.svelte.ts
export function logger(fn: () => void) {}
// @filename: logger.svelte.test.js
// ---cut---
import { flushSync } from 'svelte';
import { expect, test } from 'vitest';
import { logger } from './logger.svelte.js';
test('Effect', () => {
const cleanup = $effect.root(() => {
let count = $state(0);
// logger uses an $effect to log updates of its input
let log = logger(() => count);
// effects normally run after a microtask,
// use flushSync to execute all pending effects synchronously
flushSync();
expect(log).toEqual([0]);
count = 1;
flushSync();
expect(log).toEqual([0, 1]);
});
cleanup();
});
```
```
/// file: logger.svelte.js
/**
* @param {() => any} getValue
*/
export function logger(getValue) {
/** @type {any[]} */
let log = [];
$effect(() => {
log.push(getValue());
});
return log;
}
```
### Component testing
It is possible to test your components in isolation, which allows you to render them in a browser (real or simulated), simulate behavior, and make assertions, without spinning up your whole app.
> \[!NOTE\] Before writing component tests, think about whether you actually need to test the component, or if it's more about the logic _inside_ the component. If so, consider extracting out that logic to test it in isolation, without the overhead of a component.
To get started, install jsdom (a library that shims DOM APIs):
```
npm install -D jsdom
```
Then adjust your `vite.config.js`:
```
/// file: vite.config.js
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [
/* ... */
],
test: {
// If you are testing components client-side, you need to set up a DOM environment.
// If not all your files should have this environment, you can use a
// `// @vitest-environment jsdom` comment at the top of the test files instead.
environment: 'jsdom'
},
// Tell Vitest to use the `browser` entry points in `package.json` files, even though it's running in Node
resolve: process.env.VITEST
? {
conditions: ['browser']
}
: undefined
});
```
After that, you can create a test file in which you import the component to test, interact with it programmatically and write expectations about the results:
```
/// file: component.test.js
import { flushSync, mount, unmount } from 'svelte';
import { expect, test } from 'vitest';
import Component from './Component.svelte';
test('Component', () => {
// Instantiate the component using Svelte's `mount` API
const component = mount(Component, {
target: document.body, // `document` exists because of jsdom
props: { initial: 0 }
});
expect(document.body.innerHTML).toBe('0 ');
// Click the button, then flush the changes so you can synchronously write expectations
document.body.querySelector('button')?.click();
flushSync();
expect(document.body.innerHTML).toBe('1 ');
// Remove the component from the DOM
unmount(component);
});
```
While the process is very straightforward, it is also low level and somewhat brittle, as the precise structure of your component may change frequently. Tools like [@testing-library/svelte](https://testing-library.com/docs/svelte-testing-library/intro/) can help streamline your tests. The above test could be rewritten like this:
```
// @errors: 2339
/// file: component.test.js
import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import { expect, test } from 'vitest';
import Component from './Component.svelte';
test('Component', async () => {
const user = userEvent.setup();
render(Component);
const button = screen.getByRole('button');
expect(button).toHaveTextContent(0);
await user.click(button);
expect(button).toHaveTextContent(1);
});
```
When writing component tests that involve two-way bindings, context or snippet props, it's best to create a wrapper component for your specific test and interact with that. `@testing-library/svelte` contains some [examples](https://testing-library.com/docs/svelte-testing-library/example).
## Component tests with Storybook
[Storybook](https://storybook.js.org) is a tool for developing and documenting UI components, and it can also be used to test your components. They're run with Vitest's browser mode, which renders your components in a real browser for the most realistic testing environment.
To get started, first install Storybook ([using Svelte's CLI](/docs/cli/storybook)) in your project via `npx sv add storybook` and choose the recommended configuration that includes testing features. If you're already using Storybook, and for more information on Storybook's testing capabilities, follow the [Storybook testing docs](https://storybook.js.org/docs/writing-tests?renderer=svelte) to get started.
You can create stories for component variations and test interactions with the [play function](https://storybook.js.org/docs/writing-tests/interaction-testing?renderer=svelte#writing-interaction-tests), which allows you to simulate behavior and make assertions using the Testing Library and Vitest APIs. Here's an example of two stories that can be tested, one that renders an empty LoginForm component and one that simulates a user filling out the form:
```
/// file: LoginForm.stories.svelte
{
// Simulate a user filling out the form
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com');
await userEvent.type(canvas.getByTestId('password'), 'a-random-password');
await userEvent.click(canvas.getByRole('button'));
// Run assertions
await expect(args.onSubmit).toHaveBeenCalledTimes(1);
await expect(canvas.getByText('You’re in!')).toBeInTheDocument();
}}
/>
```
## End-to-end tests with Playwright
E2E (short for 'end to end') tests allow you to test your full application through the eyes of the user. This section uses [Playwright](https://playwright.dev/) as an example, but you can also use other solutions like [Cypress](https://www.cypress.io/) or [NightwatchJS](https://nightwatchjs.org/).
You can use the Svelte CLI to [setup Playwright](/docs/cli/playwright) either during project creation or later on. You can also [set it up with `npm init playwright`](https://playwright.dev/docs/intro). Additionally, you may also want to install an IDE plugin such as [the VS Code extension](https://playwright.dev/docs/getting-started-vscode) to be able to execute tests from inside your IDE.
If you've run `npm init playwright` or are not using Vite, you may need to adjust the Playwright config to tell Playwright what to do before running the tests — mainly starting your application at a certain port. For example:
```
/// file: playwright.config.js
const config = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
};
export default config;
```
You can now start writing tests. These are totally unaware of Svelte as a framework, so you mainly interact with the DOM and write assertions.
```
// @errors: 2307 7031
/// file: tests/hello-world.spec.js
import { expect, test } from '@playwright/test';
test('home page has expected h1', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
});
```
## [TypeScript](https://docharvest.github.io/docs/svelte/07-misc/03-typescript/)
Contents
svelte
TypeScript
Svelte
TypeScript
You can use TypeScript within Svelte components. IDE extensions like the [Svelte VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) will help you catch errors right in your editor, and [`svelte-check`](https://www.npmjs.com/package/svelte-check) does the same on the command line, which you can integrate into your CI.
## `
greet(e.target.innerText)}>
{name as string}
```
Doing so allows you to use TypeScript's _type-only_ features. That is, all features that just disappear when transpiling to JavaScript, such as type annotations or interface declarations. Features that require the TypeScript compiler to output actual code are not supported. This includes:
- using enums
- using `private`, `protected` or `public` modifiers in constructor functions together with initializers
- using features that are not yet part of the ECMAScript standard (i.e. not level 4 in the TC39 process) and therefore not implemented yet within Acorn, the parser we use for parsing JavaScript
If you want to use one of these features, you need to setup up a `script` preprocessor.
## Preprocessor setup
To use non-type-only TypeScript features within Svelte components, you need to add a preprocessor that will turn TypeScript into JavaScript.
### Using Vite
If you're using SvelteKit, or Vite _without_ SvelteKit, you can use `vitePreprocess` from `@sveltejs/vite-plugin-svelte` in your config file:
```
/// file: svelte.config.js
// @noErrors
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const config = {
// Note the additional `{ script: true }`
preprocess: vitePreprocess({ script: true })
};
export default config;
```
### Using other build tools
If you're using tools like Rollup (via [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte)) or Webpack (via [svelte-loader](https://github.com/sveltejs/svelte-loader)) instead, install `typescript` and `svelte-preprocess` and add the preprocessor to the plugin config. See the respective plugin READMEs for more info.
> \[!NOTE\] If you're starting a new project, we recommend using SvelteKit or Vite instead
## tsconfig.json settings
When using TypeScript, make sure your `tsconfig.json` is setup correctly.
- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2015` so classes are not compiled to functions
- Set [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) to `true` so that imports are left as-is
- Set [`isolatedModules`](https://www.typescriptlang.org/tsconfig/#isolatedModules) to `true` so that each file is looked at in isolation. TypeScript has a few features which require cross-file analysis and compilation, which the Svelte compiler and tooling like Vite don't do.
## Typing `$props`
Type `$props` just like a regular object with certain properties.
```
eventHandler('clicked button')}>
{@render snippetWithStringArgument('hello')}
```
## Generic `$props`
Components can declare a generic relationship between their properties. One example is a generic list component that receives a list of items and a callback property that receives an item from the list. To declare that the `items` property and the `select` callback operate on the same types, add the `generics` attribute to the `script` tag:
```
{#each items as item}
select(item)}>
{item.text}
{/each}
```
The content of `generics` is what you would put between the `<...>` tags of a generic function. In other words, you can use multiple generics, `extends` and fallback types.
## Typing wrapper components
In case you're writing a component that wraps a native element, you may want to expose all the attributes of the underlying element to the user. In that case, use (or extend from) one of the interfaces provided by `svelte/elements`. Here's an example for a `Button` component:
```
{@render children?.()}
```
Not all elements have a dedicated type definition. For those without one, use `SvelteHTMLElements`:
```
{@render children?.()}
```
## Typing `$state`
You can type `$state` like any other variable.
```
let count: number = $state(0);
```
If you don't give `$state` an initial value, part of its types will be `undefined`.
```
// @noErrors
// Error: Type 'number | undefined' is not assignable to type 'number'
let count: number = $state();
```
If you know that the variable _will_ be defined before you first use it, use an `as` casting. This is especially useful in the context of classes:
```
class Counter {
count = $state() as number;
constructor(initial: number) {
this.count = initial;
}
}
```
## The `Component` type
Svelte components are of type `Component`. You can use it and its related types to express a variety of constraints.
Using it together with dynamic components to restrict what kinds of component can be passed to it:
```
```
> \[!LEGACY\] In Svelte 4, components were of type `SvelteComponent`
To extract the properties from a component, use `ComponentProps`.
```
import type { Component, ComponentProps } from 'svelte';
import MyComponent from './MyComponent.svelte';
function withProps>(
component: TComponent,
props: ComponentProps
) {}
// Errors if the second argument is not the correct props expected
// by the component in the first argument.
withProps(MyComponent, { foo: 'bar' });
```
To declare that a variable expects the constructor or instance type of a component:
```
```
## Enhancing built-in DOM types
Svelte provides a best effort of all the HTML DOM types that exist. Sometimes you may want to use experimental attributes or custom events coming from an action. In these cases, TypeScript will throw a type error, saying that it does not know these types. If it's a non-experimental standard attribute/event, this may very well be a missing typing from our [HTML typings](https://github.com/sveltejs/svelte/blob/main/packages/svelte/elements.d.ts). In that case, you are welcome to open an issue and/or a PR fixing it.
In case this is a custom or experimental attribute/event, you can enhance the typings by augmenting the `svelte/elements` module like this:
```
/// file: additional-svelte-typings.d.ts
import { HTMLButtonAttributes } from 'svelte/elements';
declare module 'svelte/elements' {
// add a new element
export interface SvelteHTMLElements {
'custom-button': HTMLButtonAttributes;
}
// add a new global attribute that is available on all html elements
export interface HTMLAttributes {
globalattribute?: string;
}
// add a new attribute for button elements
export interface HTMLButtonAttributes {
veryexperimentalattribute?: string;
}
}
export {}; // ensure this is not an ambient module, else types will be overridden instead of augmented
```
Then make sure that the `d.ts` file is referenced in your `tsconfig.json`. If it reads something like `"include": ["src/**/*"]` and your `d.ts` file is inside `src`, it should work. You may need to reload for the changes to take effect.
## [Custom elements](https://docharvest.github.io/docs/svelte/07-misc/04-custom-elements/)
Contents
svelte
Custom elements
Svelte
Custom elements
Svelte components can also be compiled to custom elements (aka web components) using the `customElement: true` compiler option. You should specify a tag name for the component using the `` [element](svelte-options). Within the custom element you can access the host element via the [`$host`](https://svelte.dev/docs/svelte/$host) rune.
```
Hello {name}!
```
You can leave out the tag name for any of your inner components which you don't want to expose and use them like regular Svelte components. Consumers of the component can still name it afterwards if needed, using the static `element` property which contains the custom element constructor and which is available when the `customElement` compiler option is `true`.
```
// @noErrors
import MyElement from './MyElement.svelte';
customElements.define('my-element', MyElement.element);
```
Once a custom element has been defined, it can be used as a regular DOM element:
```
document.body.innerHTML = `
This is some slotted content
`;
```
Any [props](basic-markup#Component-props) are exposed as properties of the DOM element (as well as being readable/writable as attributes, where possible).
```
// @noErrors
const el = document.querySelector('my-element');
// get the current value of the 'name' prop
console.log(el.name);
// set a new value, updating the shadow DOM
el.name = 'everybody';
```
Note that you need to list out all properties explicitly, i.e. doing `let props = $props()` without declaring `props` in the [component options](#Component-options) means that Svelte can't know which props to expose as properties on the DOM element.
## Component lifecycle
Custom elements are created from Svelte components using a wrapper approach. This means the inner Svelte component has no knowledge that it is a custom element. The custom element wrapper takes care of handling its lifecycle appropriately.
When a custom element is created, the Svelte component it wraps is _not_ created right away. It is only created in the next tick after the `connectedCallback` is invoked. Properties assigned to the custom element before it is inserted into the DOM are temporarily saved and then set on component creation, so their values are not lost. The same does not work for invoking exported functions on the custom element though, they are only available after the element has mounted. If you need to invoke functions before component creation, you can work around it by using the [`extend` option](#Component-options).
When a custom element written with Svelte is created or updated, the shadow DOM will reflect the value in the next tick, not immediately. This way updates can be batched, and DOM moves which temporarily (but synchronously) detach the element from the DOM don't lead to unmounting the inner component.
The inner Svelte component is destroyed in the next tick after the `disconnectedCallback` is invoked.
## Component options
When constructing a custom element, you can tailor several aspects by defining `customElement` as an object within `` since Svelte 4. This object may contain the following properties:
- `tag: string`: an optional `tag` property for the custom element's name. If set, a custom element with this tag name will be defined with the document's `customElements` registry upon importing this component.
- `shadow`: an optional property to modify shadow root properties. It accepts the following values:
- `"none"`: No shadow root is created. Note that styles are then no longer encapsulated, and you can't use slots.
- `"open"`: Shadow root is created with the `mode: "open"` option.
- [`ShadowRootInit`](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options): You can pass a settings object that will be passed to `attachShadow()` when shadow root is created.
- `props`: an optional property to modify certain details and behaviors of your component's properties. It offers the following settings:
- `attribute: string`: To update a custom element's prop, you have two alternatives: either set the property on the custom element's reference as illustrated above or use an HTML attribute. For the latter, the default attribute name is the lowercase property name. Modify this by assigning `attribute: ""`.
- `reflect: boolean`: By default, updated prop values do not reflect back to the DOM. To enable this behavior, set `reflect: true`.
- `type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object'`: While converting an attribute value to a prop value and reflecting it back, the prop value is assumed to be a `String` by default. This may not always be accurate. For instance, for a number type, define it using `type: "Number"` You don't need to list all properties, those not listed will use the default settings.
- `extend`: an optional property which expects a function as its argument. It is passed the custom element class generated by Svelte and expects you to return a custom element class. This comes in handy if you have very specific requirements to the life cycle of the custom element or want to enhance the class to for example use [ElementInternals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals#examples) for better HTML form integration.
```
{
// Extend the class so we can let it participate in HTML forms
return class extends customElementConstructor {
static formAssociated = true;
constructor() {
super();
this.attachedInternals = this.attachInternals();
}
// Add the function here, not below in the component so that
// it's always available, not just when the inner Svelte component
// is mounted
randomIndex() {
this.elementIndex = Math.random();
}
};
}
}}
/>
...
```
> \[!NOTE\] While Typescript is supported in the `extend` function, it is subject to limitations: you need to set `lang="ts"` on one of the scripts AND you can only use [erasable syntax](https://www.typescriptlang.org/tsconfig/#erasableSyntaxOnly) in it. They are not processed by script preprocessors.
## Caveats and limitations
Custom elements can be a useful way to package components for consumption in a non-Svelte app, as they will work with vanilla HTML and JavaScript as well as [most frameworks](https://custom-elements-everywhere.com/). There are, however, some important differences to be aware of:
- Styles are _encapsulated_, rather than merely _scoped_ (unless you set `shadow: "none"`). This means that any non-component styles (such as you might have in a `global.css` file) will not apply to the custom element, including styles with the `:global(...)` modifier
- Instead of being extracted out as a separate .css file, styles are inlined into the component as a JavaScript string
- Custom elements are not generally suitable for server-side rendering, as the shadow DOM is invisible until JavaScript loads
- In Svelte, slotted content renders _lazily_. In the DOM, it renders _eagerly_. In other words, it will always be created even if the component's `` element is inside an `{#if ...}` block. Similarly, including a `` in an `{#each ...}` block will not cause the slotted content to be rendered multiple times
- 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 ` ` as `customElement.addEventListener('eworld', true)` (and not as `customElement.oneworld = true`)
## [Browser support](https://docharvest.github.io/docs/svelte/07-misc/05-browser-support/)
Contents
svelte
Browser support
Svelte
Browser support
The table below shows the minimum browser versions Svelte is expected to work in, derived from the browser APIs used by Svelte's internal code.
@include .generated/browser-support.md
This table only covers Svelte itself. It does not include [SvelteKit](/docs/kit), other Svelte libraries, or your own code.
## Exceptions
A few Svelte features require a higher minimum browser version. You'll only need to take the following table into consideration if you use these specific features.
@include .generated/browser-support-features.md
## [Svelte 4 migration guide](https://docharvest.github.io/docs/svelte/07-misc/06-v4-migration-guide/)
Contents
svelte
Svelte 4 migration guide
Svelte
Svelte 4 migration guide
This migration guide provides an overview of how to migrate from Svelte version 3 to 4. See the linked PRs for more details about each change. Use the migration script to migrate some of these automatically: `npx svelte-migrate@latest svelte-4`
If you're a library author, consider whether to only support Svelte 4 or if it's possible to support Svelte 3 too. Since most of the breaking changes don't affect many people, this may be easily possible. Also remember to update the version range in your `peerDependencies`.
## Minimum version requirements
- Upgrade to Node 16 or higher. Earlier versions are no longer supported. ([#8566](https://github.com/sveltejs/svelte/issues/8566))
- If you are using SvelteKit, upgrade to 1.20.4 or newer ([sveltejs/kit#10172](https://github.com/sveltejs/kit/pull/10172))
- If you are using Vite without SvelteKit, upgrade to `vite-plugin-svelte` 2.4.1 or newer ([#8516](https://github.com/sveltejs/svelte/issues/8516))
- If you are using webpack, upgrade to webpack 5 or higher and `svelte-loader` 3.1.8 or higher. Earlier versions are no longer supported. ([#8515](https://github.com/sveltejs/svelte/issues/8515), [198dbcf](https://github.com/sveltejs/svelte/commit/198dbcf))
- If you are using Rollup, upgrade to `rollup-plugin-svelte` 7.1.5 or higher ([198dbcf](https://github.com/sveltejs/svelte/commit/198dbcf))
- If you are using TypeScript, upgrade to TypeScript 5 or higher. Lower versions might still work, but no guarantees are made about that. ([#8488](https://github.com/sveltejs/svelte/issues/8488))
## Browser conditions for bundlers
Bundlers must now specify the `browser` condition when building a frontend bundle for the browser. SvelteKit and Vite will handle this automatically for you. If you're using any others, you may observe lifecycle callbacks such as `onMount` not get called and you'll need to update the module resolution configuration.
- For Rollup this is done within the `@rollup/plugin-node-resolve` plugin by setting `browser: true` in its options. See the [`rollup-plugin-svelte`](https://github.com/sveltejs/rollup-plugin-svelte/#usage) documentation for more details
- For webpack this is done by adding `"browser"` to the `conditionNames` array. You may also have to update your `alias` config, if you have set it. See the [`svelte-loader`](https://github.com/sveltejs/svelte-loader#usage) documentation for more details
([#8516](https://github.com/sveltejs/svelte/issues/8516))
## Removal of CJS related output
Svelte no longer supports the CommonJS (CJS) format for compiler output and has also removed the `svelte/register` hook and the CJS runtime version. If you need to stay on the CJS output format, consider using a bundler to convert Svelte's ESM output to CJS in a post-build step. ([#8613](https://github.com/sveltejs/svelte/issues/8613))
## Stricter types for Svelte functions
There are now stricter types for `createEventDispatcher`, `Action`, `ActionReturn`, and `onMount`:
- `createEventDispatcher` now supports specifying that a payload is optional, required, or non-existent, and the call sites are checked accordingly ([#7224](https://github.com/sveltejs/svelte/issues/7224))
```
// @errors: 2554 2345
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
optional: number | null;
required: string;
noArgument: null;
}>();
// Svelte version 3:
dispatch('optional');
dispatch('required'); // I can still omit the detail argument
dispatch('noArgument', 'surprise'); // I can still add a detail argument
// Svelte version 4 using TypeScript strict mode:
dispatch('optional');
dispatch('required'); // error, missing argument
dispatch('noArgument', 'surprise'); // error, cannot pass an argument
```
- `Action` and `ActionReturn` have a default parameter type of `undefined` now, which means you need to type the generic if you want to specify that this action receives a parameter. The migration script will migrate this automatically ([#7442](https://github.com/sveltejs/svelte/pull/7442))
```
// @noErrors
---const action: Action = (node, params) => { ... } // this is now an error if you use params in any way---
+++const action: Action = (node, params) => { ... } // params is of type string+++
```
- `onMount` now shows a type error if you return a function asynchronously from it, because this is likely a bug in your code where you expect the callback to be called on destroy, which it will only do for synchronously returned functions ([#8136](https://github.com/sveltejs/svelte/issues/8136))
```
// @noErrors
// Example where this change reveals an actual bug
onMount(
--- // someCleanup() not called because function handed to onMount is async
async () => {
const something = await foo();---
+++ // someCleanup() is called because function handed to onMount is sync
() => {
foo().then(something => {...});
// ...
return () => someCleanup();
}
);
```
## Custom Elements with Svelte
The creation of custom elements with Svelte has been overhauled and significantly improved. The `tag` option is deprecated in favor of the new `customElement` option:
```
--- ---
+++ +++
```
This change was made to allow [more configurability](custom-elements#Component-options) for advanced use cases. The migration script will adjust your code automatically. The update timing of properties has changed slightly as well. ([#8457](https://github.com/sveltejs/svelte/issues/8457))
## SvelteComponentTyped is deprecated
`SvelteComponentTyped` is deprecated, as `SvelteComponent` now has all its typing capabilities. Replace all instances of `SvelteComponentTyped` with `SvelteComponent`.
```
---import { SvelteComponentTyped } from 'svelte';---
+++import { SvelteComponent } from 'svelte';+++
---export class Foo extends SvelteComponentTyped<{ aProp: string }> {}---
+++export class Foo extends SvelteComponent<{ aProp: string }> {}+++
```
If you have used `SvelteComponent` as the component instance type previously, you may see a somewhat opaque type error now, which is solved by changing `: typeof SvelteComponent` to `: typeof SvelteComponent`.
```
random
```
The migration script will do both automatically for you. ([#8512](https://github.com/sveltejs/svelte/issues/8512))
## Transitions are local by default
Transitions are now local by default to prevent confusion around page navigations. "local" means that a transition will not play if it's within a nested control flow block (`each/if/await/key`) and not the direct parent block but a block above it is created/destroyed. In the following example, the `slide` intro animation will only play when `success` goes from `false` to `true`, but it will _not_ play when `show` goes from `false` to `true`:
```
{#if show}
...
{#if success}
Success
{/each}
{/if}
```
To make transitions global, add the `|global` modifier — then they will play when _any_ control flow block above is created/destroyed. The migration script will do this automatically for you. ([#6686](https://github.com/sveltejs/svelte/issues/6686))
## Default slot bindings
Default slot bindings are no longer exposed to named slots and vice versa:
```
count in default slot — is available: {count}
count in bar slot — is not available: {count}
```
This makes slot bindings more consistent as the behavior is undefined when for example the default slot is from a list and the named slot is not. ([#6049](https://github.com/sveltejs/svelte/issues/6049))
## Preprocessors
The order in which preprocessors are applied has changed. Now, preprocessors are executed in order, and within one group, the order is markup, script, style.
```
// @errors: 2304
import { preprocess } from 'svelte/compiler';
const { code } = await preprocess(
source,
[
{
markup: () => {
console.log('markup-1');
},
script: () => {
console.log('script-1');
},
style: () => {
console.log('style-1');
}
},
{
markup: () => {
console.log('markup-2');
},
script: () => {
console.log('script-2');
},
style: () => {
console.log('style-2');
}
}
],
{
filename: 'App.svelte'
}
);
// Svelte 3 logs:
// markup-1
// markup-2
// script-1
// script-2
// style-1
// style-2
// Svelte 4 logs:
// markup-1
// script-1
// style-1
// markup-2
// script-2
// style-2
```
This could affect you for example if you are using `MDsveX` - in which case you should make sure it comes before any script or style preprocessor.
```
// @noErrors
preprocess: [
--- vitePreprocess(),
mdsvex(mdsvexConfig)---
+++ mdsvex(mdsvexConfig),
vitePreprocess()+++
]
```
Each preprocessor must also have a name. ([#8618](https://github.com/sveltejs/svelte/issues/8618))
## New eslint package
`eslint-plugin-svelte3` is deprecated. It may still work with Svelte 4 but we make no guarantees about that. We recommend switching to our new package [eslint-plugin-svelte](https://github.com/sveltejs/eslint-plugin-svelte). See [this Github post](https://github.com/sveltejs/kit/issues/10242#issuecomment-1610798405) for an instruction how to migrate. Alternatively, you can create a new project using `npm create svelte@latest`, select the eslint (and possibly TypeScript) option and then copy over the related files into your existing project.
## Other breaking changes
- the `inert` attribute is now applied to outroing elements to make them invisible to assistive technology and prevent interaction. ([#8628](https://github.com/sveltejs/svelte/pull/8628))
- the runtime now uses `classList.toggle(name, boolean)` which may not work in very old browsers. Consider using a [polyfill](https://github.com/eligrey/classList.js) if you need to support these browsers. ([#8629](https://github.com/sveltejs/svelte/issues/8629))
- the runtime now uses the `CustomEvent` constructor which may not work in very old browsers. Consider using a [polyfill](https://github.com/theftprevention/event-constructor-polyfill/tree/master) if you need to support these browsers. ([#8775](https://github.com/sveltejs/svelte/pull/8775))
- people implementing their own stores from scratch using the `StartStopNotifier` interface (which is passed to the create function of `writable` etc) from `svelte/store` now need to pass an update function in addition to the set function. This has no effect on people using stores or creating stores using the existing Svelte stores. ([#6750](https://github.com/sveltejs/svelte/issues/6750))
- `derived` will now throw an error on falsy values instead of stores passed to it. ([#7947](https://github.com/sveltejs/svelte/issues/7947))
- type definitions for `svelte/internal` were removed to further discourage usage of those internal methods which are not public API. Most of these will likely change for Svelte 5
- Removal of DOM nodes is now batched which slightly changes its order, which might affect the order of events fired if you're using a `MutationObserver` on these elements ([#8763](https://github.com/sveltejs/svelte/pull/8763))
- if you enhanced the global typings through the `svelte.JSX` namespace before, you need to migrate this to use the `svelteHTML` namespace. Similarly if you used the `svelte.JSX` namespace to use type definitions from it, you need to migrate those to use the types from `svelte/elements` instead. You can find more information about what to do [here](https://github.com/sveltejs/language-tools/blob/master/docs/preprocessors/typescript.md#im-getting-deprecation-warnings-for-sveltejsx--i-want-to-migrate-to-the-new-typings)
## [Svelte 5 migration guide](https://docharvest.github.io/docs/svelte/07-misc/07-v5-migration-guide/)
Contents
svelte
Svelte 5 migration guide
Svelte
Svelte 5 migration guide
Version 5 comes with an overhauled syntax and reactivity system. While it may look different at first, you'll soon notice many similarities. This guide goes over the changes in detail and shows you how to upgrade. Along with it, we also provide information on _why_ we did these changes.
You don't have to migrate to the new syntax right away — Svelte 5 still supports the old Svelte 4 syntax, and you can mix and match components using the new syntax with components using the old and vice versa. We expect many people to be able to upgrade with only a few lines of code changed initially. There's also a [migration script](#Migration-script) that helps you with many of these steps automatically.
## Reactivity syntax changes
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
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`:
```
```
Nothing else changes. `count` is still the number itself, and you read and write directly to it, without a wrapper like `.value` or `getCount()`.
> \[!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
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:
```
```
As with `$state`, nothing else changes. `double` is still the number itself, and you read it directly, without a wrapper like `.value` or `getDouble()`.
A `$:` statement could also be used to create side effects. In Svelte 5, this is achieved using the `$effect` rune:
```
```
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.
>
> There were also gotchas that were hard to spot:
>
> - `$:` only updated directly before rendering, which meant you could read stale values in-between rerenders
> - `$:` only ran once per tick, which meant that statements may run less often than you think
> - `$:` dependencies were determined through static analysis of the dependencies. This worked in most cases, but could break in subtle ways during a refactoring where dependencies would be for example moved into a function and no longer be visible as a result
> - `$:` statements were also ordered by using static analysis of the dependencies. In some cases there could be ties and the ordering would be wrong as a result, needing manual interventions. Ordering could also break while refactoring code and some dependencies no longer being visible as a result.
>
> Lastly, it wasn't TypeScript-friendly (our editor tooling had to jump through some hoops to make it valid for TypeScript), which was a blocker for making Svelte's reactivity model truly universal.
>
> `$derived` and `$effect` fix all of these by
>
> - always returning the latest value
> - running as often as needed to be stable
> - determining the dependencies at runtime, and therefore being immune to refactorings
> - executing dependencies as needed and therefore being immune to ordering problems
> - being TypeScript-friendly
### 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:
```
```
There are multiple cases where declaring properties becomes less straightforward than having a few `export let` declarations:
- you want to rename the property, for example because the name is a reserved identifier (e.g. `class`)
- you don't know which other properties to expect in advance
- you want to forward every property to another component
All these cases need special syntax in Svelte 4:
- renaming: `export { klass as class}`
- other properties: `$$restProps`
- all properties `$$props`
In Svelte 5, the `$props` rune makes this straightforward without any additional Svelte-specific syntax:
- renaming: use property renaming `let { class: klass } = $props();`
- other properties: use spreading `let { foo, bar, ...rest } = $props();`
- all properties: don't destructure `let props = $props();`
```
click me
```
> \[!DETAILS\] Why we did this `export let` was one of the more controversial API decisions, and there was a lot of debate about whether you should think about a property being `export`ed or `import`ed. `$props` doesn't have this trait. It's also in line with the other runes, and the general thinking reduces to "everything special to reactivity in Svelte is a rune".
>
> There were also a lot of limitations around `export let`, which required additional API, as shown above. `$props` unite this in one syntactical concept that leans heavily on regular JavaScript destructuring syntax.
## Event changes
Event handlers have been given a facelift in Svelte 5. Whereas in Svelte 4 we use the `on:` directive to attach an event listener to an element, in Svelte 5 they are properties like any other (in other words — remove the colon):
```
count++}>
clicks: {count}
```
Since they're just properties, you can use the normal shorthand syntax...
```
clicks: {count}
```
...though when using a named event handler function it's usually better to use a more descriptive name.
### Component events
In Svelte 4, components could emit events by creating a dispatcher with `createEventDispatcher`.
This function is deprecated in Svelte 5. Instead, components should accept _callback props_ — which means you then pass functions as properties to these components:
```
{
size += power---.detail---;
if (size > 75) burst = true;
}}
---on:---deflate={(power) => {
if (size > 0) size -= power---.detail---;
}}
/>
{#if burst}
new balloon
💥
{:else}
🎈
{/if}
```
```
---dispatch('inflate', power)---+++inflate(power)+++}>
inflate
---dispatch('deflate', power)---+++deflate(power)+++}>
deflate
power--}>-
Pump power: {power}
power++}>+
```
### Bubbling events
Instead of doing `` to 'forward' the event from the element to the component, the component should accept an `onclick` callback prop:
```
click me
```
Note that this also means you can 'spread' event handlers onto the element along with other props instead of tediously forwarding each event separately:
```
click me
```
### Event modifiers
In Svelte 4, you can add event modifiers to handlers:
```
...
```
Modifiers are specific to `on:` and so do not work with modern event handlers. Adding things like `event.preventDefault()` inside the handler itself is preferable, since all the logic lives in one place rather than being split between handler and modifiers.
Since event handlers are just functions, you can create your own wrappers as necessary:
```
...
```
There are three modifiers — `capture`, `passive` and `nonpassive` — that can't be expressed as wrapper functions, since they need to be applied when the event handler is bound rather than when it runs.
For `capture`, we add the modifier to the event name:
```
...
```
Changing the [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) option of an event handler, meanwhile, is not something to be done lightly. If you have a use case for it — and you probably don't! — then you will need to use an action to apply the event handler yourself.
### Multiple event handlers
In Svelte 4, this is possible:
```
...
```
Duplicate attributes/properties on elements — which now includes event handlers — are not allowed. Instead, do this:
```
{
one(e);
two(e);
}}
>
...
```
When spreading props, local event handlers must go _after_ the spread, or they risk being overwritten:
```
{
doStuff(e);
props.onclick?.(e);
}}
>
...
```
> \[!DETAILS\] Why we did this `createEventDispatcher` was always a bit boilerplate-y:
>
> - import the function
> - call the function to get a dispatch function
> - call said dispatch function with a string and possibly a payload
> - retrieve said payload on the other end through a `.detail` property, because the event itself was always a `CustomEvent`
>
> It was always possible to use component callback props, but because you had to listen to DOM events using `on:`, it made sense to use `createEventDispatcher` for component events due to syntactical consistency. Now that we have event attributes (`onclick`), it's the other way around: Callback props are now the more sensible thing to do.
>
> The removal of event modifiers is arguably one of the changes that seems like a step back for those who've liked the shorthand syntax of event modifiers. Given that they are not used that frequently, we traded a smaller surface area for more explicitness. Modifiers also were inconsistent, because most of them were only usable on DOM elements.
>
> Multiple listeners for the same event are also no longer possible, but it was something of an anti-pattern anyway, since it impedes readability: if there are many attributes, it becomes harder to spot that there are two handlers unless they are right next to each other. It also implies that the two handlers are independent, when in fact something like `event.stopImmediatePropagation()` inside `one` would prevent `two` from being called.
>
> By deprecating `createEventDispatcher` and the `on:` directive in favour of callback props and normal element properties, we:
>
> - reduce Svelte's learning curve
> - remove boilerplate, particularly around `createEventDispatcher`
> - remove the overhead of creating `CustomEvent` objects for events that may not even have listeners
> - add the ability to spread event handlers
> - add the ability to know which event handlers were provided to a component
> - add the ability to express whether a given event handler is required or optional
> - increase type safety (previously, it was effectively impossible for Svelte to guarantee that a component didn't emit a particular event)
## Snippets instead of slots
In Svelte 4, content can be passed to components using slots. Svelte 5 replaces them with snippets, which are more powerful and flexible, and so slots are deprecated in Svelte 5.
They continue to work, however, and you can pass snippets to a component that uses slots:
```
```
```
default child content
{#snippet foo({ message })}
message from child: {message}
{/snippet}
```
(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 ` ` 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.
### Default content
In Svelte 4, the easiest way to pass a piece of UI to the child was using a ` `. In Svelte 5, this is done using the `children` prop instead, which is then shown with `{@render children()}`:
```
--- ---
+++{@render children?.()}+++
```
### Multiple content placeholders
If you wanted multiple UI placeholders, you had to use named slots. In Svelte 5, use props instead, name them however you like and `{@render ...}` them:
```
--- ---
+++{@render header()}+++
--- ---
+++{@render main()}+++
--- ---
+++{@render footer()}+++
```
### Passing data back up
In Svelte 4, you would pass data to a ` ` and then retrieve it with `let:` in the parent component. In Svelte 5, snippets take on that responsibility:
```
+++{#snippet item(text)}+++
{text}
+++{/snippet}+++
---No items yet ---
+++{#snippet empty()}
No items yet
{/snippet}+++
```
```
{#if items.length}
{#each items as entry}
--- ---
+++{@render item(entry)}+++
{/each}
{:else}
--- ---
+++{@render empty?.()}+++
{/if}
```
> \[!DETAILS\] Why we did this Slots were easy to get started with, but the more advanced the use case became, the more involved and confusing the syntax became:
>
> - the `let:` syntax was confusing to many people as it _creates_ a variable whereas all other `:` directives _receive_ a variable
> - the scope of a variable declared with `let:` wasn't clear. In the example above, it may look like you can use the `item` slot prop in the `empty` slot, but that's not true
> - named slots had to be applied to an element using the `slot` attribute. Sometimes you didn't want to create an element, so we had to add the `` API
> - named slots could also be applied to a component, which changed the semantics of where `let:` directives are available (even today us maintainers often don't know which way around it works)
>
> Snippets solve all of these problems by being much more readable and clear. At the same time they're more powerful as they allow you to define sections of UI that you can render _anywhere_, not just passing them as props to a component.
## Migration script
By now you should have a pretty good understanding of the before/after and how the old syntax relates to the new syntax. It probably also became clear that a lot of these migrations are rather technical and repetitive — something you don't want to do by hand.
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 (` ` → `{@render children()}`)
- migrate slot usages to snippets (`...
` → `{#snippet x()}...
{/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.
Not everything can be migrated automatically, and some migrations need manual cleanup afterwards. The following sections describe these in more detail.
### run
You may see that the migration script converts some of your `$:` statements to a `run` function which is imported from `svelte/legacy`. This happens if the migration script couldn't reliably migrate the statement to a `$derived` and concluded this is a side effect instead. In some cases this may be wrong and it's best to change this to use a `$derived` instead. In other cases it may be right, but since `$:` statements also ran on the server but `$effect` does not, it isn't safe to transform it as such. Instead, `run` is used as a stopgap solution. `run` mimics most of the characteristics of `$:`, in that it runs on the server once, and runs as `$effect.pre` on the client (`$effect.pre` runs _before_ changes are applied to the DOM; most likely you want to use `$effect` instead).
```
```
### Event modifiers
Event modifiers are not applicable to event attributes (e.g. you can't do `onclick|preventDefault={...}`). Therefore, when migrating event directives to event attributes, we need a function-replacement for these modifiers. These are imported from `svelte/legacy`, and should be migrated away from in favor of e.g. just using `event.preventDefault()`.
```
{
+++event.preventDefault();+++
// ...
})}
>
click me
```
### Things that are not automigrated
The migration script does not convert `createEventDispatcher`. You need to adjust those parts manually. It doesn't do it because it's too risky because it could result in breakage for users of the component, which the migration script cannot find out.
The migration script does not convert `beforeUpdate/afterUpdate`. It doesn't do it because it's impossible to determine the actual intent of the code. As a rule of thumb you can often go with a combination of `$effect.pre` (runs at the same time as `beforeUpdate` did) and `tick` (imported from `svelte`, allows you to wait until changes are applied to the DOM and then do some work).
## Components are no longer classes
In Svelte 3 and 4, components are classes. In Svelte 5 they are functions and should be instantiated differently. If you need to manually instantiate components, you should use `mount` or `hydrate` (imported from `svelte`) instead. If you see this error using SvelteKit, try updating to the latest version of SvelteKit first, which adds support for Svelte 5. If you're using Svelte without SvelteKit, you'll likely have a `main.js` file (or similar) which you need to adjust:
```
+++import { mount } from 'svelte';+++
import App from './App.svelte'
---const app = new App({ target: document.getElementById("app") });---
+++const app = mount(App, { target: document.getElementById("app") });+++
export default app;
```
`mount` and `hydrate` have the exact same API. The difference is that `hydrate` will pick up the Svelte's server-rendered HTML inside its target and hydrate it. Both return an object with the exports of the component and potentially property accessors (if compiled with `accessors: true`). They do not come with the `$on`, `$set` and `$destroy` methods you may know from the class component API. These are its replacements:
For `$on`, instead of listening to events, pass them via the `events` property on the options argument.
```
+++import { mount } from 'svelte';+++
import App from './App.svelte'
---const app = new App({ target: document.getElementById("app") });
app.$on('event', callback);---
+++const app = mount(App, { target: document.getElementById("app"), events: { event: callback } });+++
```
> \[!NOTE\] Note that using `events` is discouraged — instead, [use callbacks](#Event-changes)
For `$set`, use `$state` instead to create a reactive property object and manipulate it. If you're doing this inside a `.js` or `.ts` file, adjust the ending to include `.svelte`, i.e. `.svelte.js` or `.svelte.ts`.
```
+++import { mount } from 'svelte';+++
import App from './App.svelte'
---const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
app.$set({ foo: 'baz' });---
+++const props = $state({ foo: 'bar' });
const app = mount(App, { target: document.getElementById("app"), props });
props.foo = 'baz';+++
```
For `$destroy`, use `unmount` instead.
```
+++import { mount, unmount } from 'svelte';+++
import App from './App.svelte'
---const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
app.$destroy();---
+++const app = mount(App, { target: document.getElementById("app") });
unmount(app);+++
```
As a stop-gap-solution, you can also use `createClassComponent` or `asClassComponent` (imported from `svelte/legacy`) instead to keep the same API known from Svelte 4 after instantiating.
```
+++import { createClassComponent } from 'svelte/legacy';+++
import App from './App.svelte'
---const app = new App({ target: document.getElementById("app") });---
+++const app = createClassComponent({ component: App, target: document.getElementById("app") });+++
export default app;
```
If this component is not under your control, you can use the `compatibility.componentApi` compiler option for auto-applied backwards compatibility, which means code using `new Component(...)` keeps working without adjustments (note that this adds a bit of overhead to each component). This will also add `$set` and `$on` methods for all component instances you get through `bind:this`.
```
/// svelte.config.js
export default {
compilerOptions: {
compatibility: {
componentApi: 4
}
}
};
```
Note that `mount` and `hydrate` are _not_ synchronous, so things like `onMount` won't have been called by the time the function returns and the pending block of promises will not have been rendered yet (because `#await` waits a microtask to wait for a potentially immediately-resolved promise). If you need that guarantee, call `flushSync` (import from `'svelte'`) after calling `mount/hydrate`.
### Server API changes
Similarly, components no longer have a `render` method when compiled for server-side rendering. Instead, pass the function to `render` from `svelte/server`:
```
+++import { render } from 'svelte/server';+++
import App from './App.svelte';
---const { html, head } = App.render({ props: { message: 'hello' }});---
+++const { html, head } = render(App, { props: { message: 'hello' }});+++
```
In Svelte 4, rendering a component to a string also returned the CSS of all components. In Svelte 5, this is no longer the case by default because most of the time you're using a tooling chain that takes care of it in other ways (like SvelteKit). If you need CSS to be returned from `render`, you can set the `css` compiler option to `'injected'` and it will add `
```
In Svelte 3/4 using `$$props` and `$$restProps` creates a modest performance penalty, so they should only be used when needed.
## [on:](https://docharvest.github.io/docs/svelte/99-legacy/10-legacy-on/)
Contents
svelte
on:
Svelte
on:
In runes mode, event handlers are just like any other attribute or prop.
In legacy mode, we use the `on:` directive:
```
count: {count}
```
Handlers can be declared inline with no performance penalty:
```
(count += 1)}>
count: {count}
```
Add _modifiers_ to element event handlers with the `|` character.
```
```
The following modifiers are available:
- `preventDefault` — calls `event.preventDefault()` before running the handler
- `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element
- `stopImmediatePropagation` — calls `event.stopImmediatePropagation()`, preventing other listeners of the same event from being fired.
- `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
- `nonpassive` — explicitly set `passive: false`
- `capture` — fires the handler during the _capture_ phase instead of the _bubbling_ phase
- `once` — remove the handler after the first time it runs
- `self` — only trigger handler if `event.target` is the element itself
- `trusted` — only trigger handler if `event.isTrusted` is `true`. I.e. if the event is triggered by a user action.
Modifiers can be chained together, e.g. `on:click|once|capture={...}`.
If the `on:` directive is used without a value, the component will _forward_ the event, meaning that a consumer of the component can listen for it.
```
The component itself will emit the click event
```
It's possible to have multiple event listeners for the same event:
```
clicks: {count}
```
## Component events
Components can dispatch events by creating a _dispatcher_ when they are initialised:
```
dispatch('decrement')}>decrement
dispatch('increment')}>increment
```
`dispatch` creates a [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). If a second argument is provided, it becomes the `detail` property of the event object.
A consumer of this component can listen for the dispatched events:
```
n -= 1}
on:increment={() => n += 1}
/>
n: {n}
```
Component events do not bubble — a parent component can only listen for events on its immediate children.
Other than `once`, modifiers are not valid on component event handlers.
> \[!NOTE\] If you're planning an eventual migration to Svelte 5, use callback props instead. This will make upgrading easier as `createEventDispatcher` is deprecated:
>
> ```
>
>
>
> decrement
> increment
> ```
## [ · docharvest](https://docharvest.github.io/docs/svelte/99-legacy/20-legacy-slots/)
## [$$slots](https://docharvest.github.io/docs/svelte/99-legacy/21-legacy-$$slots/)
Contents
svelte
$$slots
Svelte
$$slots
In runes mode, we know which [snippets](snippet) were provided to a component, as they're just normal props.
In legacy mode, the way to know if content was provided for a given slot is with the `$$slots` object, whose keys are the names of the slots passed into the component by the parent.
```
{#if $$slots.description}
{/if}
```
```
Blog Post Title
```
## [svelte:fragment](https://docharvest.github.io/docs/svelte/99-legacy/22-legacy-svelte-fragment/)
Contents
svelte
Svelte
The `` element allows you to place content in a [named slot](legacy-slots) without wrapping it in a container DOM element. This keeps the flow layout of your document intact.
```
No header was provided
Some content between header and footer
```
```
Hello
All rights reserved.
Copyright (c) 2019 Svelte Industries
```
> \[!NOTE\] In Svelte 5+, this concept is obsolete, as snippets don't create a wrapping element
## [svelte:component](https://docharvest.github.io/docs/svelte/99-legacy/30-legacy-svelte-component/)
Contents
svelte
Svelte
In runes mode, `` 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 ``, which destroys and recreates the component instance when the value of its `this` expression changes:
```
```
If `this` is falsy, no component is rendered.
## [svelte:self](https://docharvest.github.io/docs/svelte/99-legacy/31-legacy-svelte-self/)
Contents
svelte
Svelte
The `` element allows a component to include itself, recursively.
It cannot appear at the top level of your markup; it must be inside an if or each block or passed to a component's slot to prevent an infinite loop.
```
{#if count > 0}
counting down... {count}
{:else}
lift-off!
{/if}
```
> \[!NOTE\] This concept is obsolete, as components can import themselves:
>
> ```
>
>
>
> {#if count > 0}
> counting down... {count}
>
> {:else}
> lift-off!
> {/if}
> ```
## [Imperative component API](https://docharvest.github.io/docs/svelte/99-legacy/40-legacy-component-api/)
Contents
svelte
Imperative component API
Svelte
Imperative component API
In Svelte 3 and 4, the API for interacting with a component is different than in Svelte 5. Note that this page does _not_ apply to legacy mode components in a Svelte 5 application.
## Creating a component
```
// @noErrors
const component = new Component(options);
```
A client-side component — that is, a component compiled with `generate: 'dom'` (or the `generate` option left unspecified) is a JavaScript class.
```
// @noErrors
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
// assuming App.svelte contains something like
// `export let answer`:
answer: 42
}
});
```
The following initialisation options can be provided:
option
default
description
`target`
**none**
An `HTMLElement` or `ShadowRoot` to render to. This option is required
`anchor`
`null`
A child of `target` to render the component immediately before
`props`
`{}`
An object of properties to supply to the component
`context`
`new Map()`
A `Map` of root-level context key-value pairs to supply to the component
`hydrate`
`false`
See below
`intro`
`false`
If `true`, will play transitions on initial render, rather than waiting for subsequent state changes
Existing children of `target` are left where they are.
The `hydrate` option instructs Svelte to upgrade existing DOM (usually from server-side rendering) rather than creating new elements. It will only work if the component was compiled with the [`hydratable: true` option](/docs/svelte-compiler#compile). Hydration of `` elements only works properly if the server-side rendering code was also compiled with `hydratable: true`, which adds a marker to each element in the `` so that the component knows which elements it's responsible for removing during hydration.
Whereas children of `target` are normally left alone, `hydrate: true` will cause any children to be removed. For that reason, the `anchor` option cannot be used alongside `hydrate: true`.
The existing DOM doesn't need to match the component — Svelte will 'repair' the DOM as it goes.
```
/// file: index.js
// @noErrors
import App from './App.svelte';
const app = new App({
target: document.querySelector('#server-rendered-html'),
hydrate: true
});
```
> \[!NOTE\] In Svelte 5+, use [`mount`](svelte#mount) instead
## `$set`
```
// @noErrors
component.$set(props);
```
Programmatically sets props on an instance. `component.$set({ x: 1 })` is equivalent to `x = 1` inside the component's `