Matthew Tyson
Contributing Writer

Reactive magic in Svelte 5: Understanding Runes

how-to
Feb 14, 20247 mins
Development Libraries and FrameworksJavaScriptWeb Development

Here's a preview of Runesโ€”a big new idea for reactive coding and the most important change to Svelte in years.

Magic, runes, Svelte 5. Five runes in a magician's hand.
Credit: STEKLO/Shutterstock

Svelte 5 brings improvements under the hoodโ€”namely functional components and the adoption of signalsโ€”but is otherwise a mostly incremental update. The one exception is the new Runes feature, which introduces a host of ideas for dealing with reactivity in a more modular, succinct, and fine-grained way.ย 

In this article, youโ€™ll get a hands-on introduction to the main runes shipping with Svelte 5: $state(), $derived(), $props(), $inspectI(), and $effect().

Runes in Svelte

At first glance, it might seem that the new runes feature adds complexity to working with Svelte. In fact, this idea offers a simpler approach to doing many things you likely already do.ย The term rune refers to a magical glyph, or an alphabetic letter with mysterious powers. In Svelte, runes are special tokens that tell the Svelte compiler to work behind the scenes in specific ways to make things happen.ย 

A rune gives you a simple syntax for telling the Svelte engine to do specific, useful work like managing state and exposing component properties.

The main runes introduced in Svelte 5 are:

  • $state()
  • $derived()
  • $effect()
  • $props()
  • $inspect()

As you can see, a rune is a function prefixed with a dollar-sign character.ย As the developer, you use these special functions almost exactly like you would any other function. The Svelte engine then takes care of implementing the runeโ€™s intended action for you behind the scenes.

$state()

Letโ€™s begin by looking at $state(), which is the rune you will likely use most. In a (very) loose sense, the $state rune does something logically similar to the React useState() hook, providing a functional way to deal with reactive state.

Letโ€™s consider a simple example.ย Hereโ€™s how youโ€™d create an input and display its value in Svelte 4, without runes:


<script>
  let text = "Default";
</script>

<input type="text" bind:value={text}/>
Text: {text}

And now, here is the same action with the $state rune:


<script>
	let text = $state("Default")
</script>

<input type="text" bind:value={text}/>
Text: {text}

In both of these samples, we have a simple state variable (text) and use it to drive a text input that outputs to the screen.ย This is typical Svelte code, especially the bind:value={text} syntax, which gives you a simple way to two-way bind to an input.

The only change here is that, instead of declaring a normal variable with let text = "Default", we declare it as a state rune: let text = $state("Default"). The "Default" we pass into $state is the initial value.

Notice that the bind:value call doesnโ€™t have to change at all: Svelte knows how to use a rune in that context.ย In general, the state rune reference acts properly anywhere a variable would work.

Although this is a small change, there is an obvious benefit in clarity.ย Itโ€™s neat that the Svelte compiler magically realizes that let count = 0 should be reactive when itโ€™s at the top of a component. But as the codebase grows, that feature is a bit obfuscating, as it gets hard to tell which variables are reactive.ย The $state rune eliminates that issue.

Another benefit is that $state can appear anywhere, not just the top level of your components.ย So, letโ€™s say we wanted a factory function that would create text states for use in arbitrary inputs.ย Hereโ€™s a simple example:


<script>
	let makeText = function(def){
		let myText = $state(def);
		return { 
			get text() { return myText },
			set text(text) { myText = text },
		}
	}
	let text = makeText("test");
</script>

<input type="text" bind:value={text.text}/>
Text: {text.text}

While the example is contrived, the point is the $state() declaration creates a functioning reactive state from inside a different scopeโ€”something that requires contortions in the old Svelte syntax. Also notice that in this case, we provided both a getter and a setter for the text variable; this is because the bind:value call is a two-way binding requiring both read and write access to the state object.

Another interesting property of the $state() rune is that it is automatically wired to members of an object:


<script>
	let valueObject = new class { 
		text = $state('I am a test')
		num = $state(42)
	};
</script>

<input type="text" bind:value={valueObject.text}/>
<input type="number" bind:value={valueObject.num}/>
<br>
Text: {valueObject.text}
<br>
Number: {valueObject.num}

The essence of this snippet is that the text and num properties of the valueObject class are automatically bound properly to the inputs, without explicitly declaring the getters and setters.ย Svelte automatically provides the getters and setters the object needs to access the properties of the valueObject class.

$derived()

In the past, you could create a derived property using the $: syntax in Svelte.ย This had some limitations, including that values could get stale because the engine only updated the computed value when the component updated.ย Svelte 5 replaces the $: syntax with $derived(), which keeps the computed value in sync at all times.ย ย 

Hereโ€™s an example of using $derived to combine strings from text inputs:


<script>
	let greeting = $state("Hello there");
	let name = $state("User");
	let sentence = $derived(greeting + " " + name);
</script>

<input type="text" bind:value={greeting}/>
<input type="text" bind:value={name}/>
<br>
Text: {sentence }

What we are doing here is using the sentence variable as a derived rune. It is derived from the greeting and name state runes. So, a derived rune combines the states of state variables.

Using $derived(greeting + โ€œ โ€œ + name) ensures that whenever the greeting or name changes, the sentence variable will reflect those changes.

$effect()

$effect is a rune that works similarly to Reactโ€™s useState() effect.ย It is used to cause effects outside of the reactive engine.ย Hereโ€™s an example from the Svelte docs:


$effect(() => {
  // runs when the component is mounted, and again
  // whenever `count` or `doubled` change,
  // after the DOM has been updated
  console.log({ count, doubled });

  return () => {
   // if a callback is provided, it will run
   // a) immediately before the effect re-runs
   // b) when the component is destroyed
	console.log('cleanup');
  };
});

The purpose of this code is to run logging when the component is first mounted, and then whenever the dependent variables count and doubled are modified.ย The optional return value lets you do any necessary cleanup before the effect runs or when the component is unmounted.

$props()

$props() is the new way to declare and consume component properties in Svelte.ย This covers a few use cases, especially exporting variables at the top level of components with let.ย An example is worth a thousand words, and in general the new $props syntax is clean and obvious:


// main.svelte
<script>
  import Component2 from './Component2.svelte';
</script>
<Component2>
</Component2>
<Component2 prop2 = "test">
</Component2>

// Component2.svelte
<script>
let { prop1 = "foo", prop2} = $props();
</script>
{prop1}
<br>
{prop2}

//outputs:
foo
foo
test

Here, the main.svelte component is importing Component2 and demonstrating how to modify the props via properties on the markup.ย Notice that Component2 can declare default values like prop1 = โ€œfooโ€.

$inspect()

The last rune weโ€™ll look at is $inspect.ย This is a kind of reactive console logging statement:


<script>
	let count = $state(0);
	let message = $state('hello');

	$inspect(count, message); // will console.log when `count` or `message` change
</script>

<button onclick={() => count++}>Increment</button>
<input bind:value={message} />

In this example (taken from the Svelte docs), the purpose is to emit a logging statement whenever the count of message variables changes.ย In essence, it gives you a simple way to log to the console reactively, in response to variable updates.

Conclusion

The overall effect of runes is to simplify the Svelte API for developers.ย It will take some time to adjust to the new syntax and migrate existing code, but in general, the new approach really is easier.ย If there is an exception, itโ€™s the $effect() rune, which requires a bit more thought before being used to replace existing approaches.ย The elegance of $state(), $derived(), and $props() more than make up for $effect()โ€˜s complexity. All in all the, new Runes feature is a fresh and welcome idea in reactivity.

Matthew Tyson
Contributing Writer

Matthew Tyson is a contributing writer at InfoWorld. A seasoned technology journalist and expert in enterprise software development, Matthew has written about programming, programming languages, language frameworks, application platforms, development tools, databases, cryptography, information security, cloud computing, and emerging technologies such as blockchain and machine learning for more than 15 years. His work has appeared in leading publications including InfoWorld, CIO, CSO Online, and IBM developerWorks. Matthew also has had the privilege of interviewing many tech luminaries including Brendan Eich, Grady Booch, Guillermo Rauch, and Martin Hellman.

Matthewโ€™s diverse background encompasses full-stack development (Java, JVM languages such as Kotlin, JavaScript, Python, .NET), front-end development (Angular, React, Vue, Svelte) and back-end development (Spring Boot, Node.js, Django), software architecture, and IT infrastructure at companies ranging from startups to Fortune 500 enterprises. He is a trusted authority in critical technology areas such as database design (SQL and NoSQL), AI-assisted coding, agentic AI, open-source initiatives, enterprise integration, and cloud platforms, providing insightful analysis and practical guidance rooted in real-world experience.

More from this author