---
title: $state
---
The `$state` rune allows you to create _reactive state_, which means that your UI _reacts_ when it changes.
```svelte
```
Unlike other frameworks you may have encountered, there is no API for interacting with state — `count` is just a number, rather than an object or a function, and you can update it like you would update any other variable.
### Deep state
If `$state` is used with an array or a simple object, the result is a deeply reactive _state proxy_. [Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) allow Svelte to run code when you read or write properties, including via methods like `array.push(...)`, triggering granular updates.
> [!NOTE] Classes like `Set` and `Map` will not be proxied, but Svelte provides reactive implementations for various built-ins like these that can be imported from [`svelte/reactivity`](./svelte-reactivity).
State is proxified recursively until Svelte finds something other than an array or simple object. In a case like this...
```js
let todos = $state([
{
done: false,
text: 'add more todos'
}
]);
```
...modifying an individual todo's property will trigger updates to anything in your UI that depends on that specific property:
```js
// @filename: ambient.d.ts
declare global {
const todos: Array<{ done: boolean, text: string }>
}
// @filename: index.js
// ---cut---
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
// ---cut---
todos.push({
done: false,
text: 'eat lunch'
});
```
> [!NOTE] When you update properties of proxies, the original object is _not_ mutated.
Since `$state` stops at boundaries that are not simple arrays or objects, the following will not trigger any reactivity:
```svelte
```
You can however use `$state` _inside_ the class to make it work, as explained in the next section.
### Classes
You can 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;
}
reset() {
this.text = '';
this.done = false;
}
}
```
Under the hood, the compiler transforms `done` and `text` into `get`/`set` methods on the class prototype referencing private fields. That means the properties are _not_ enumerable.
## `$state.raw`
In cases where you don't want objects and arrays to be deeply reactive you can use `$state.raw`.
State declared with `$state.raw` cannot be mutated; it can only be _reassigned_. In other words, rather than assigning to a property of an object, or using an array method like `push`, replace the object or array altogether if you'd like to update it:
```js
let person = $state.raw({
name: 'Heraclitus',
age: 49
});
// this will have no effect
person.age += 1;
// this will work, because we're creating a new person
person = {
name: 'Heraclitus',
age: 50
};
```
This can improve performance with large arrays and objects that you weren't planning to mutate anyway, since it avoids the cost of making them reactive. Note that raw state can _contain_ reactive state (for example, a raw array of reactive objects).
## Passing `$state` across boundaries
Since there's no wrapper around `$state`, `$state.raw`, or [`$derived`]($derived), you have to be aware of keeping reactivity alive when passing it across boundaries — e.g. when you pass a reactive object into or out of a function. The most succinct way of thinking about this is to treat `$state`, `$state.raw`, and [`$derived`]($derived) as "just JavaScript", and reuse the knowledge of how normal JavaScript variables work when crossing boundaries. Take the following example:
```js
// @errors: 7006
function createTodo(initial) {
let text = initial;
let done = false;
return {
text,
done,
log: () => console.log(text, done)
}
}
const todo = createTodo('wrong');
todo.log(); // logs "'wrong', false"
todo.done = true;
todo.log(); // still logs "'wrong', false"
```
The value change does not propagate back into the function body of `createTodo`, because `text` and `done` are read at the point of return, and are therefore a fixed value. To make that work, we have to bring the read and write into the scope of the function body. This can be done via getter/setters or via function calls:
```js
// @errors: 7006
function createTodo(initial) {
let text = initial;
let done = false;
return {
// using getter/setter
get text() { return text },
set text(v) { text = v },
// using functions
isDone() { return done },
toggle() { done = !done },
// log
log: () => console.log(text, done)
}
}
const todo = createTodo('right');
todo.log(); // logs "'right', false"
todo.text = 'changed'; // invokes the setter
todo.toggle(); // invokes the function
todo.log(); // logs "'changed', true"
```
What you could also do is to instead create an object and return that as a whole. While the variable itself is fixed in time, its properties are not, and so they can be changed from the outside and the changes are observable from within the function:
```js
// @errors: 7006
function createTodo(initial) {
const todo = { text: initial, done: false }
return {
todo,
log: () => console.log(todo.text, todo.done)
}
}
const todo = createTodo('right');
todo.log(); // logs "'right', false"
todo.todo.done = true; // mutates the object
todo.log(); // logs "'right', true"
```
Classes are similar, their properties are "live" due to the `this` context:
```js
// @errors: 7006
class Todo {
done = false;
text;
constructor(text) {
this.text = text;
}
log() {
console.log(this.done, this.text)
}
}
const todo = new Todo('right');
todo.log(); // logs "'right', false"
todo.done = true;
todo.log(); // logs "'right', true"
```
Notice how we didn't use _any_ Svelte specifics, this is just regular JavaScript semantics. `$state` and `$state.raw` (and [`$derived`]($derived)) don't change these, they just add reactivity on top, so that when you change a variable something can happen in reaction to it.
As a consequence, the answer to preserving reactivity across boundaries is to use getters/setters or functions (in case of `$state`, `$state.raw` and `$derived`), an object with mutable properties (in case of `$state`), or a class with reactive properties.
```js
// @errors: 7006
/// file: getters-setters-functions.svelte.js
function doubler(count) {
const double = $derived(count() * 2)
return {
get current() { return double }
};
}
let count = $state(0);
const double = doubler(() => count);
$effect(() => console.log(double.current)); // $effect logs 0
count = 1; // $effect logs 2
```
```js
// @errors: 7006
/// file: mutable-object.svelte.js
function logger(value) {
$effect(() => console.log(value.current));
}
let count = $state({ current: 0 });
logger(count); // $effect logs 0
count.current = 1; // $effect logs 1
```
```js
// @errors: 7006
/// file: class.svelte.js
function logger(counter) {
$effect(() => console.log(counter.count));
}
class Counter {
count = $state(0);
increment() { this.count++; }
}
let counter = new Counter();
logger(counter); // $effect logs 0
counter.increment(); // $effect logs 1
```
For the same reasons, you should not destructure reactive objects — their value is read at that point in time, and not updated anymore from inside whatever created it.
```js
// @errors: 7006
class Counter {
count = $state(0);
increment = () => { this.count++; }
}
// don't do this
let { count, increment } = new Counter();
count; // 0
increment();
count; // still 0
// do this instead
let counter = new Counter();
counter.count; // 0
counter.increment();
counter.count; // 1
```
## `$state.snapshot`
To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`:
```svelte
```
This is handy when you want to pass some state to an external library or API that doesn't expect a proxy, such as `structuredClone`.