Svelte and SvelteKit course Β· Module 4: Props and Events

Context API

3 min read Β·
In this lesson5

For deeply nested components, the Context API lets you avoid "prop drilling".

Problem: Prop Drilling

App
└── Layout
    └── Sidebar
        └── Navigation
            └── NavItem  <-- needs data from App!

Passing props through every level is cumbersome.

Solution: Context

<!-- App.svelte -->
<script>
  import { setContext } from 'svelte';
  import Layout from './Layout.svelte';

  const user = $state({
    name: 'Ragnar',
    role: 'jarl',
    gold: 1000
  });

  // Set context - available to all descendants
  setContext('user', user);
</script>

<Layout />
<button onclick={() => user.gold += 100}>+100 gold</button>
<!-- NavItem.svelte (deeply nested) -->
<script>
  import { getContext } from 'svelte';

  // Get context - works at any level!
  const user = getContext('user');
</script>

<div class="nav-item">
  <span>{user.name}</span>
  <span>({user.role}, {user.gold} gold)</span>
</div>

The context holds a $state object, so descendants see its changes. Change its fields (user.gold += 100) instead of assigning a new object - otherwise descendants keep the old one.

Typed Context

It pays to keep context keys and their functions in one file. Since Svelte 5.40 createContext does this for you - it returns a pair of functions, with no manual key:

// context.ts
import { createContext } from 'svelte';

interface User {
  name: string;
  role: string;
}

export const [getUserContext, setUserContext] = createContext<User>();

In older versions of Svelte 5 you write the same with setContext and getContext and a unique key:

// context.js
import { getContext, setContext } from 'svelte';

const USER_KEY = Symbol('user');

export function setUserContext(user) {
  setContext(USER_KEY, user);
}

export function getUserContext() {
  return getContext(USER_KEY);
}
<!-- App.svelte -->
<script>
  import { setUserContext } from './context.js';

  const user = $state({ name: 'Ragnar', role: 'jarl' });
  setUserContext(user);
</script>
<!-- DeepChild.svelte -->
<script>
  import { getUserContext } from './context.js';

  const user = getUserContext();
</script>

<p>{user.name}</p>

Context vs Props vs Global State

MethodWhen to use
PropsDirect parent-child
ContextDeep hierarchy, per-tree data
Global state$state in a .svelte.js file or stores (module 8), multiple component trees

Example: Theme Context

<!-- ThemeProvider.svelte -->
<script>
  import { setContext } from 'svelte';

  let { children } = $props();

  let theme = $state('dark');

  setContext('theme', {
    get current() { return theme; },
    toggle() { theme = theme === 'dark' ? 'light' : 'dark'; }
  });
</script>

<div class="theme-{theme}">
  {@render children()}
</div>
<!-- ThemedButton.svelte -->
<script>
  import { getContext } from 'svelte';

  const theme = getContext('theme');
</script>

<button class={theme.current} onclick={theme.toggle}>
  Change theme ({theme.current})
</button>

The current getter always returns the latest value of theme, so the button refreshes after every theme change.

Context is like secret passages in the palace of Asgard - data flows without obstacles!

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. When should you use the Context API instead of props?

  2. 2. Which functions from the svelte package set and get a context by key?

Hands-on tasks in the game

  • Click in order

    When to use each: props (direct), context (deep), global state (stores or .svelte.js)

Useful articles