Why Variables Aren’t Enough

If you’re coming from vanilla JavaScript, you might expect that changing a variable automatically updates everything that uses it. In console-based programs or simple scripts, this mental model works fine — you change a value, and the next time you reference it, you get the new value.

But web UIs are different. The HTML you see on screen is a snapshot of your data at a specific moment in time. When you change a JavaScript variable, the browser doesn’t know it should look at that HTML again and update it. There’s a fundamental disconnect between your JavaScript state and what users see.

This lesson explains this disconnect, why it exists, and how Svelte’s $state rune bridges the gap to create truly reactive user interfaces.


What You’ll Learn

  • Why regular variables don’t trigger UI updates
  • What “reactivity” means in Svelte
  • How the $state rune creates reactive state

The Problem: Static Variables

Let’s experience this disconnect firsthand by building a simple counter with plain JavaScript. This example will show you exactly why regular variables don’t work for building interactive UIs.

<!-- filename: src/routes/+page.svelte -->
<script>
	let count = 0

	function increment() {
		count = count + 1
		console.log(count) // This logs correctly!
	}
</script>

<p>Count: {count}</p>
<button onclick={increment}>Add One</button>

What Happens When You Run This

  1. Initial render: Svelte reads count (which is 0) and puts “Count: 0” in the HTML
  2. You click the button: The increment function runs
  3. The variable changes: count becomes 1, then 2, then 3
  4. The console confirms it: Check your browser console — you’ll see the numbers increasing
  5. But the page stays frozen: The display still shows “Count: 0”

This is confusing! The count variable is clearly changing (we can see it in the console), but the number on the page never updates. Why?

The Core Issue

The problem is that JavaScript variables are not connected to the DOM. When Svelte first renders your component, it reads the value of count and writes “Count: 0” into the HTML. After that, Svelte has no idea that count exists or that it might change.

Think of it like taking a photo: the photo captures what things looked like at one moment. If someone moves after you take the photo, the photo doesn’t magically update to show their new position. Similarly, the HTML captured the value of count at one moment (when the page loaded), and it has no way to know the variable changed later.


Why This Happens

To understand why regular variables don’t update the UI, you need to understand how Svelte (and most UI frameworks) work under the hood.

The Rendering Process

When Svelte compiles your component:

  1. Build time: Svelte analyzes your code and generates optimized JavaScript
  2. Runtime (page load): Your component runs, variables are initialized, and the initial HTML is created
  3. Display: The browser shows that HTML to users
  4. After that: JavaScript continues running, but the HTML is already rendered

When you click the button and change count, that happens in step 4 — after the HTML is already on screen. The browser has no idea it should re-run the rendering process.

JavaScript Variables Are “Silent”

Regular JavaScript variables don’t announce when they change. There’s no built-in mechanism where a variable says “Hey! I just changed from 0 to 1!” That’s just not how JavaScript works.

Consider this code:

let count = 0
count = 1 // Nothing "listens" to this change

The assignment happens, the new value is stored, but there’s no event, no notification, no way for other code to detect the change automatically.

The Whiteboard Analogy

Think of it like this: you write a number on a whiteboard. Someone else copies that number onto paper. Later, you erase and write a new number on the whiteboard. The paper still shows the old number — it doesn’t magically update just because the whiteboard changed.

The whiteboard is your JavaScript variable. The paper is the HTML displayed to users. Changing the whiteboard (variable) doesn’t automatically change the paper (DOM).

What Svelte Needs

Svelte needs a signal that says “this value changed, update the UI.” It needs reactive state — variables that can broadcast their changes so Svelte knows when to update the DOM. That’s exactly what the $state rune provides.


The Solution: $state

Svelte 5 introduces runes — special instructions that tell Svelte how to handle data. One of these is the $state rune, which creates reactive state:

<!-- filename: src/routes/+page.svelte -->

<script>
	let count = $state(0)

	function increment() {
		count = count + 1
	}
</script>

<p>Count: {count}</p>
<button onclick={increment}>Add One</button>

You can test it out on Svelte REPL

Now click the button. The display updates: 1, 2, 3, 4…

The only change was wrapping 0 in $state(). That single addition tells Svelte: “Watch this variable. When it changes, update anything that uses it.”


How $state Works

Now let’s peek behind the curtain and understand what $state actually does to make reactivity work.

The Magic of $state

When you write let count = $state(0):

  1. Svelte creates a reactive signal initialized to 0 — this is a special wrapper around your value
  2. Svelte scans your template and tracks everywhere count is used (like in <p>Count: {count}</p>)
  3. Svelte sets up a subscription — when count changes, Svelte knows which parts of the DOM need updating
  4. When you assign a new value to count (like count = count + 1), Svelte intercepts that assignment
  5. Svelte updates the DOM — but only the specific parts that use count, not the entire page

You Still Write Normal JavaScript

The beauty of $state is that you use count exactly like a normal variable:

<script>
	let count = $state(0)

	// Read it like normal
	console.log(count) // 0

	// Assign to it like normal
	count = 5

	// Use it in expressions like normal
	let doubled = count * 2
</script>

You don’t need to call special getter/setter methods. You don’t need to wrap every read and write in function calls. The reactivity happens automatically behind the scenes, thanks to Svelte’s compiler.

Efficient Updates

One crucial detail: when count changes, Svelte doesn’t re-render your entire component. It only updates the specific DOM nodes that display count. This makes Svelte applications incredibly fast.

If you have:

<h1>Welcome to BookIt</h1><p>Count: {count}</p><footer>© 2025 BookIt</footer>

Changing count only updates the <p> tag, not the heading or footer. Svelte knows exactly what needs to change.


What About BookIt?

Now let’s connect this concept to the actual BookIt application you’re building. Understanding reactivity is crucial for creating the booking form.

The Booking Form Requirements

BookIt needs a booking form where users will:

  • Type their name
  • Enter their email address
  • Select a service from a dropdown
  • Choose a date and time
  • See a live preview of their booking details as they fill it out

That last requirement — live preview — is only possible with reactive state.

Without Reactive State

Imagine building the form with regular variables:

<script>
	let customerName = '' // Regular variable
</script>

<input type="text" bind:value={customerName} /><p>Booking for: {customerName}</p>

The input would work (you could type into it), but the <p> tag showing the preview would stay empty. It would read customerName once when the page loads (when it’s an empty string) and never update.

With Reactive State

Now with $state:

<script>
	let customerName = $state('') // Reactive state
</script>

<input type="text" bind:value={customerName} /><p>Booking for: {customerName}</p>

Every keystroke updates customerName, and Svelte automatically updates the preview. The user sees their name appear in real-time as they type. This is what makes modern web apps feel responsive and alive.

Building on This Foundation

In the next lessons, we’ll:

  • Create a complete booking form with multiple $state variables
  • Wire up inputs with two-way binding
  • Build a live preview panel that updates as users type
  • Handle form submission

For now, the key insight is: $state is what makes dynamic UIs possible. Every interactive part of BookIt — from the booking form to search filters to date pickers — will use reactive state.


When to Use $state

Use $state when:

  • The value will change during the component’s lifetime
  • The UI should update when the value changes
  • User input needs to be tracked

Don’t bother with $state for:

  • Constants that never change
  • Values only used in calculations, not displayed
  • Data passed in as props (handled differently)
<script>
	// ✅ Reactive — changes based on user input
	let searchQuery = $state('')

	// ❌ Not needed — this never changes
	let API_URL = 'https://api.bookit.example'

	// ❌ Not needed — constant configuration
	const MAX_BOOKINGS = 10
</script>

Common Mistakes

Forgetting $state

<script>
	// ❌ Won't update the UI
	let name = ''

	// ✅ UI updates when name changes
	let name = $state('')
</script>

If your UI isn’t updating when you expect, check whether you forgot $state.

Using $state for Everything

<script>
	// ❌ Overkill — this never changes
	let title = $state('BookIt')

	// ✅ Simple constant
	let title = 'BookIt'
</script>

Only use $state for values that actually need to be reactive.


Summary

Regular JavaScript variables don’t trigger UI updates. Svelte’s $state rune creates reactive state that automatically updates the DOM when values change. This is the foundation of interactive Svelte applications.

Key takeaways:

  • Plain variables render once and don’t update the UI
  • $state(initialValue) creates reactive state
  • Use $state for values that change and affect the display

Next Steps

Now that you understand reactivity, let’s put it to use. Continue with Create a Booking Form Component to start building BookIt’s core feature.