Svelte and SvelteKit course ยท Module 3: Reactivity and Runes

$state - Reactive State

2 min read ยท
In this lesson5

$state is the most powerful rune - it creates reactive state that automatically updates the UI on changes.

Basic Usage

<script>
  // Number
  let gold = $state(0);

  // String
  let vikingName = $state('Ragnar');

  // Boolean
  let isReady = $state(false);

  // Array
  let warriors = $state(['Bjorn', 'Ivar']);

  // Object
  let ship = $state({
    name: 'Drakkar',
    crew: 50,
    cargo: []
  });
</script>

Updating State

<script>
  let count = $state(0);
  let items = $state(['a', 'b']);
  let user = $state({ name: 'Viking', level: 1 });

  function examples() {
    // Numbers - like regular assignment
    count += 1;
    count = count * 2;

    // Arrays - mutations work!
    items.push('c');        // Works in Svelte 5!
    items[0] = 'x';         // Works!
    items = [...items, 'd']; // Also works

    // Objects - mutations work!
    user.level += 1;        // Works!
    user.name = 'Jarl';     // Works!
  }
</script>

Deep Reactivity

$state creates a deeply reactive object:

<script>
  let kingdom = $state({
    name: 'Nordheim',
    villages: [
      { name: 'Svenheim', population: 500 },
      { name: 'Bjornvik', population: 300 }
    ]
  });

  function growVillage() {
    // Nested mutation - works automatically!
    kingdom.villages[0].population += 100;
  }
</script>

<p>{kingdom.villages[0].name}: {kingdom.villages[0].population}</p>
<button onclick={growVillage}>Grow village</button>

$state with Classes

<script>
  class Warrior {
    name = $state('');
    health = $state(100);
    weapon = $state('Sword');

    constructor(name) {
      this.name = name;
    }

    takeDamage(amount) {
      this.health -= amount;
    }
  }

  let hero = new Warrior('Ragnar');
</script>

<p>{hero.name}: {hero.health} HP</p>
<button onclick={() => hero.takeDamage(10)}>
  Deal damage
</button>

$state.raw - Without Deep Reactivity

Sometimes you do not need deep reactivity:

<script>
  // Only the reference is reactive, not the contents
  let battleLog = $state.raw([
    { year: 793, place: 'Lindisfarne' },
    { year: 845, place: 'Paris' }
  ]);

  // battleLog.push(...) will NOT refresh the view - assign a new array
  function addBattle() {
    battleLog = [...battleLog, { year: 866, place: 'York' }];
  }
</script>

<p>Battles in the chronicle: {battleLog.length}</p>
<button onclick={addBattle}>Add a battle</button>

Use $state.raw for:

  • Large datasets
  • Read-only data
  • Performance optimization
Code for this lesson: App.svelte
<script>
  // $state - deep reactivity

  let warrior = $state({
    name: 'Ragnar',
    stats: {
      health: 100,
      strength: 95,
      energy: 80
    },
    inventory: ['Axe', 'Shield']
  });

  function takeDamage() {
    // Nested mutations work!
    warrior.stats.health -= 10;
  }

  function heal() {
    warrior.stats.health = Math.min(100, warrior.stats.health + 20);
  }

  function addItem(item) {
    warrior.inventory.push(item);
  }
</script>

<main class="warrior-panel">
  <h1>{warrior.name}</h1>

  <section class="stats">
    <h2>Stats</h2>
    <div class="stat">
      <span class="stat-label">Health</span>
      <div class="bar">
        <div class="fill health" style="width: {warrior.stats.health}%"></div>
      </div>
      <span>{warrior.stats.health}/100</span>
    </div>

    <div class="stat">
      <span class="stat-label">Strength</span>
      <div class="bar">
        <div class="fill strength" style="width: {warrior.stats.strength}%"></div>
      </div>
      <span>{warrior.stats.strength}/100</span>
    </div>

    <div class="actions">
      <button onclick={takeDamage} class="damage">Take damage</button>
      <button onclick={heal} class="heal">Heal</button>
    </div>
  </section>

  <section class="inventory">
    <h2>Inventory ({warrior.inventory.length})</h2>
    <ul>
      {#each warrior.inventory as item}
        <li>{item}</li>
      {/each}
    </ul>
    <button onclick={() => addItem('Sword')}>+ Add Sword</button>
  </section>
</main>

<style>
  .warrior-panel {
    font-family: 'Georgia', serif;
    max-width: 500px;
    margin: 0 auto;
    padding: 2rem;
    background: #1a0f0a;
    color: #e8d5b7;
    min-height: 100vh;
  }

  h1 {
    color: #d4af37;
    text-align: center;
  }

  h2 {
    color: #d4af37;
    font-size: 1rem;
    margin-bottom: 1rem;
  }

  section {
    background: rgba(212, 175, 55, 0.1);
    border: 1px solid #d4af37;
    border-radius: 8px;
    padding: 1rem;
    margin-bottom: 1rem;
  }

  .stat {
    margin-bottom: 0.75rem;
  }

  .stat .stat-label {
    display: block;
    margin-bottom: 0.25rem;
  }

  .bar {
    height: 20px;
    background: rgba(0,0,0,0.5);
    border-radius: 4px;
    overflow: hidden;
  }

  .fill {
    height: 100%;
    transition: width 0.3s;
  }

  .fill.health { background: linear-gradient(90deg, #8b0000, #ff4444); }
  .fill.strength { background: linear-gradient(90deg, #d4af37, #ffd700); }

  .actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 1rem;
  }

  button {
    flex: 1;
    padding: 0.5rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    color: white;
  }

  .damage { background: #8b0000; }
  .heal { background: #2d5016; }

  ul {
    list-style: none;
    padding: 0;
  }

  li {
    padding: 0.5rem;
    background: rgba(0,0,0,0.3);
    margin: 0.25rem 0;
    border-radius: 4px;
  }
</style>

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How do you create reactive state in Svelte 5?

  2. 2. What is true about $state in Svelte 5?

Hands-on tasks in the game

  • Code editor

    Complete the code by creating a reactive array of warriors

Useful articles