The Purest Use Case for Context

Theme management is one of the purest use cases for context. A theme affects every visual component in an application, needs to change dynamically, and shouldn’t require prop drilling through dozens of components. Context gives you exactly what theming needs: ambient data that any descendant can access without explicit wiring.

Consider the Simpler Alternative First

If you don’t need nested theme overrides—different parts of your component tree using different themes simultaneously—consider the simpler useTheme hook pattern instead. It uses module-level reactive state, requires no providers, and covers 95% of use cases in under 100 lines of code. This article covers the Context approach for when you specifically need nested providers.

By the end of this article, you’ll have a theme system that supports light, dark, and system-preference modes, automatically detects and responds to operating system theme changes, persists user preferences to localStorage, loads without any “flash of wrong theme,” works correctly with server-side rendering, and supports local theme overrides through nested providers.

The key architectural insight: CSS is the single source of truth for colors. JavaScript only manages which theme is active by setting the data-theme attribute. The browser handles the rest natively and efficiently.


The Problem Space

Before writing code, consider what makes theming genuinely difficult. The obvious approach—passing a theme prop through every component—fails immediately in any real application. With navigation, sidebars, cards, buttons, forms, modals, and tooltips all needing theme information, you’d be threading the same prop through every single component in your tree.

Context solves the distribution problem, but theming has additional complications. First, there’s the preference versus resolution distinction. When a user selects “system” as their theme preference, components need an actual theme—light or dark—to render. Something must resolve the preference into a concrete theme.

Second, the system preference isn’t static. Users change their system theme while your app is running. Your theme system must listen for these changes and respond in real-time.

Third, server-side rendering creates a timing problem. The server renders HTML without knowing the user’s preference. By the time JavaScript loads and reads localStorage, the browser has already painted the page with the wrong theme. Users see a jarring flash.

Fourth, nested overrides are a legitimate requirement. A dark-themed promotional card on a light page requires different parts of the component tree to use different themes simultaneously. This is the primary reason to use Context over module-level state.


What Makes a Good Theme System?

Before diving into implementation, let’s establish clear requirements. A production-quality theme system should meet these criteria:

Functional Requirements

RequirementDescription
Multiple modesSupport at least light and dark themes
System preferenceDetect OS-level color scheme preference
User overrideAllow users to choose their preferred theme
PersistenceRemember user choice across sessions
Real-time syncRespond when system preference changes
Easy component accessSimple API for any component to read theme

User Experience Requirements

RequirementDescription
No flashPage loads with correct theme immediately
Smooth transitionsTheme changes animate gracefully
ConsistentAll components reflect theme simultaneously
AccessibleTheme controls have proper ARIA attributes

Developer Experience Requirements

RequirementDescription
Type-safeFull TypeScript support with inference
EncapsulatedImplementation details hidden from consumers
ComposableWorks with CSS variables, Tailwind, or direct access
SSR-compatibleWorks with SvelteKit’s server rendering

With these requirements in mind, let’s design our solution.


Theme Architecture Overview

Loading diagram...

The key insight: CSS handles all color definitions and switching. JavaScript’s only job is to set the data-theme attribute. This is simpler, faster, and what all production libraries use.


The Three-Mode Model

Most applications need three theme settings: Light (always use light theme), Dark (always use dark theme), and System (follow the operating system’s preference).

Loading diagram...

Components never handle the 'system' case themselves—they always receive a definitive 'light' or 'dark'.


CSS: The Single Source of Truth

Define all theme colors in your global CSS. This is the only place colors are defined:

/* src/app.css */

/* Light theme (default) */
:root {
	--color-background: #ffffff;
	--color-surface: #f8fafc;
	--color-surface-hover: #f1f5f9;
	--color-foreground: #0f172a;
	--color-foreground-muted: #475569;
	--color-foreground-subtle: #94a3b8;
	--color-primary: #2563eb;
	--color-primary-hover: #1d4ed8;
	--color-primary-foreground: #ffffff;
	--color-success: #16a34a;
	--color-warning: #ca8a04;
	--color-error: #dc2626;
	--color-info: #0284c7;
	--color-border: #e2e8f0;
	--color-border-focus: #3b82f6;
	--color-shadow: rgba(0, 0, 0, 0.1);
}

/* Dark theme */
:root[data-theme='dark'] {
	--color-background: #0f172a;
	--color-surface: #1e293b;
	--color-surface-hover: #334155;
	--color-foreground: #f8fafc;
	--color-foreground-muted: #cbd5e1;
	--color-foreground-subtle: #64748b;
	--color-primary: #3b82f6;
	--color-primary-hover: #60a5fa;
	--color-primary-foreground: #ffffff;
	--color-success: #22c55e;
	--color-warning: #eab308;
	--color-error: #ef4444;
	--color-info: #0ea5e9;
	--color-border: #334155;
	--color-border-focus: #60a5fa;
	--color-shadow: rgba(0, 0, 0, 0.4);
}

/* Base styles */
html,
body {
	margin: 0;
	background-color: var(--color-background);
	color: var(--color-foreground);
	font-family:
		system-ui,
		-apple-system,
		sans-serif;
	line-height: 1.5;
	transition:
		background-color 0.3s ease,
		color 0.3s ease;
}
Why CSS is Better

Single source of truth — Colors defined once, no duplication in TypeScript objects.

Browser-native performance — The browser handles CSS variable switching natively—no JavaScript loops or setProperty calls.

DevTools friendly — See all variables in the Styles panel, easy to debug.

Works before JavaScript — With proper SSR, themes work even if JS fails to load.


When JavaScript-Based CSS Properties ARE Needed

The CSS-first approach works for 95% of applications. However, there are legitimate cases where you need JavaScript to set CSS custom properties dynamically:

Use CaseWhy JavaScript Is Needed
User-customizable colorsColor picker where users choose their own palette—colors aren’t known at build time
API-driven themesColors fetched from database, CMS, or brand management system
Runtime computationGenerating tints, shades, or color scales programmatically (e.g., chroma.js)
Third-party integrationLibraries that need color values in JS (Chart.js, D3, Canvas APIs)
White-label productsSaaS apps where each tenant has custom branding loaded at runtime
Rule of Thumb

If colors are known at build time → Define them in CSS.

If colors are determined at runtime → Use JavaScript to set them.

Most apps have static color palettes, so CSS-first is the default choice.

For detailed implementations of each use case—including color pickers, API-driven themes, Chroma.js palette generation, Chart.js integration, and white-label SaaS patterns—see the dedicated article: Dynamic Theme Customization: Runtime CSS Properties.


Building the Theme Context

Now let’s build the context. Notice how simple it is—JavaScript only manages state and sets the data-theme attribute:

// src/lib/theme/types.ts

/** User-facing theme options */
export type ThemePreference = 'light' | 'dark' | 'system'

/** Actual rendered theme after resolving 'system' */
export type ResolvedTheme = 'light' | 'dark'

/** Theme context shape exposed to consumers */
export interface ThemeContext {
	readonly mode: ResolvedTheme
	readonly preference: ThemePreference
	readonly isDark: boolean
	readonly isLight: boolean
	readonly isSystem: boolean
	setPreference: (theme: ThemePreference) => void
	toggle: () => void
	reset: () => void
}
// src/lib/theme/theme-context.svelte.ts

import { setContext, getContext, hasContext } from 'svelte'
import { browser } from '$app/environment'
import type { ThemePreference, ResolvedTheme, ThemeContext } from './types.js'

const THEME_KEY = Symbol('theme')
const STORAGE_KEY = 'theme-preference'
const COOKIE_MAX_AGE = 60 * 60 * 24 * 365 // 1 year

function getSystemPreference(): ResolvedTheme {
	if (!browser) return 'light'
	return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}

function loadSavedPreference(): ThemePreference | null {
	if (!browser) return null
	const saved = localStorage.getItem(STORAGE_KEY)
	if (saved === 'light' || saved === 'dark' || saved === 'system') {
		return saved
	}
	return null
}

interface CreateThemeOptions {
	/** Force a specific theme (for nested overrides) */
	forceTheme?: ResolvedTheme
}

/**
 * Creates the theme context. Call once in root layout,
 * or nested for local theme overrides.
 */
export function createThemeContext(options: CreateThemeOptions = {}): ThemeContext {
	const { forceTheme } = options

	// State
	let systemMode = $state<ResolvedTheme>(getSystemPreference())
	let preference = $state<ThemePreference>(loadSavedPreference() ?? 'system')

	// Derived values
	let mode = $derived.by<ResolvedTheme>(() => {
		if (forceTheme) return forceTheme
		if (preference === 'system') return systemMode
		return preference
	})

	let isDark = $derived(mode === 'dark')
	let isLight = $derived(mode === 'light')
	let isSystem = $derived(preference === 'system')

	// Effect: Listen for system preference changes
	$effect(() => {
		if (!browser) return

		const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')

		function handleChange(event: MediaQueryListEvent) {
			systemMode = event.matches ? 'dark' : 'light'
		}

		systemMode = mediaQuery.matches ? 'dark' : 'light'
		mediaQuery.addEventListener('change', handleChange)

		return () => mediaQuery.removeEventListener('change', handleChange)
	})

	// Effect: Apply theme (skip for nested providers)
	$effect(() => {
		if (!browser || forceTheme) return

		// This is ALL JavaScript needs to do!
		document.documentElement.dataset.theme = mode
		document.documentElement.style.colorScheme = mode
	})

	// Effect: Persist preference (skip for nested providers)
	$effect(() => {
		if (!browser || forceTheme) return

		localStorage.setItem(STORAGE_KEY, preference)
		document.cookie = `${STORAGE_KEY}=${preference};path=/;max-age=${COOKIE_MAX_AGE};SameSite=Lax`
	})

	// Effect: Update meta theme-color for mobile browsers
	$effect(() => {
		if (!browser || forceTheme) return

		let meta = document.querySelector('meta[name="theme-color"]')
		if (!meta) {
			meta = document.createElement('meta')
			meta.setAttribute('name', 'theme-color')
			document.head.appendChild(meta)
		}

		// Read the CSS variable value
		const bg = getComputedStyle(document.documentElement)
			.getPropertyValue('--color-background')
			.trim()
		meta.setAttribute('content', bg)
	})

	// Build context with getters for reactivity
	const context: ThemeContext = {
		get mode() {
			return mode
		},
		get preference() {
			return preference
		},
		get isDark() {
			return isDark
		},
		get isLight() {
			return isLight
		},
		get isSystem() {
			return isSystem
		},

		setPreference(pref: ThemePreference) {
			preference = pref
		},

		toggle() {
			if (preference === 'system') {
				preference = mode === 'dark' ? 'light' : 'dark'
			} else {
				preference = preference === 'dark' ? 'light' : 'dark'
			}
		},

		reset() {
			preference = 'system'
		}
	}

	return setContext(THEME_KEY, context)
}

export function getThemeContext(): ThemeContext {
	if (!hasContext(THEME_KEY)) {
		throw new Error('Theme context not found. Wrap your app in ThemeProvider.')
	}
	return getContext(THEME_KEY)
}

export function hasThemeContext(): boolean {
	return hasContext(THEME_KEY)
}

Notice what’s not here:

  • No ThemeColors type
  • No lightColors / darkColors objects
  • No applyCSSProperties function
  • No loops setting CSS properties

JavaScript just sets data-theme and CSS handles everything else.


Creating the Provider Component

<!-- src/lib/theme/ThemeProvider.svelte -->
<script lang="ts">
	import { createThemeContext } from './theme-context.svelte.js'
	import type { ResolvedTheme } from './types.js'
	import type { Snippet } from 'svelte'

	interface Props {
		/** Force a specific theme (for nested overrides) */
		forceTheme?: ResolvedTheme
		children: Snippet
	}

	let { forceTheme, children }: Props = $props()

	const theme = createThemeContext({ forceTheme })
</script>

<!-- For nested providers, set data-theme on the wrapper -->
{#if forceTheme}
	<div class="theme-scope" data-theme={theme.mode}>
		{@render children()}
	</div>
{:else}
	{@render children()}
{/if}

<style>
	.theme-scope {
		background-color: var(--color-background);
		color: var(--color-foreground);
		transition:
			background-color 0.3s ease,
			color 0.3s ease;
	}
</style>

Using the Provider

<!-- src/routes/+layout.svelte -->
<script>
	import ThemeProvider from '$lib/theme/ThemeProvider.svelte'
	import '../app.css'

	let { children } = $props()
</script>

<ThemeProvider>
	{@render children()}
</ThemeProvider>

Nested Theme Overrides

This is the main reason to use Context over module-level state. Context’s scoping allows nested providers:

<!-- src/routes/+page.svelte -->
<script>
	import ThemeProvider from '$lib/theme/ThemeProvider.svelte'
</script>

<!-- Page uses global theme (light) -->
<div class="page">
	<h1>Welcome</h1>

	<!-- This section forces dark theme -->
	<ThemeProvider forceTheme="dark">
		<section class="promo-card">
			<h2>Featured Content</h2>
			<p>This card is always dark, even on a light page.</p>
		</section>
	</ThemeProvider>

	<!-- This follows the page theme -->
	<section class="regular-card">
		<h2>Regular Content</h2>
		<p>This card follows the page theme.</p>
	</section>
</div>

<style>
	.promo-card,
	.regular-card {
		background-color: var(--color-surface);
		color: var(--color-foreground);
		border: 1px solid var(--color-border);
		padding: 1.5rem;
		border-radius: 12px;
		margin: 1rem 0;
	}
</style>

The nested ThemeProvider sets data-theme="dark" on its wrapper div. CSS custom properties cascade, so everything inside uses dark colors while everything outside stays light.

Loading diagram...

Preventing Flash of Wrong Theme

On the server, we don’t have localStorage or matchMedia. The theme will flash as it corrects itself during hydration. To fix this we can add an inline script to app.html header that runs before the page renders, setting the correct theme immediately:

<!-- src/app.html -->
<!doctype html>
<html lang="en" data-theme="">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />

		<!-- CRITICAL: Runs before page renders -->
		<script>
			;(function () {
				try {
					const stored = localStorage.getItem('theme-preference')
					const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches

					let theme
					if (stored === 'dark' || stored === 'light') {
						theme = stored
					} else {
						theme = systemDark ? 'dark' : 'light'
					}

					document.documentElement.dataset.theme = theme
					document.documentElement.style.colorScheme = theme
				} catch (e) {
					// localStorage might be blocked
				}
			})()
		</script>

		%sveltekit.head%
	</head>
	<body data-sveltekit-preload-data="hover">
		<div style="display: contents">%sveltekit.body%</div>
	</body>
</html>

Because his script runs synchronously before the page paints anything, users will see the correct theme immediately with no flash.

There is one caveat: If a user has JavaScript disabled, the inline script won’t run and the theme will default to light. To support this edge case, we can read the theme from a cookie on the server and inject it into the rendered HTML:

// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit'

export const handle: Handle = async ({ event, resolve }) => {
	const theme = event.cookies.get('theme-preference')

	if (!theme || !['light', 'dark'].includes(theme)) {
		return resolve(event)
	}

	return resolve(event, {
		transformPageChunk: ({ html }) => {
			return html.replace('data-theme=""', `data-theme="${theme}"`)
		}
	})
}

Now even users without JavaScript will see their preferred theme on the first paint.


Building Theme Controls

Simple Toggle Button

<!-- src/lib/theme/ThemeToggle.svelte -->
<script lang="ts">
	import { getThemeContext } from './theme-context.svelte.js'

	const theme = getThemeContext()

	let label = $derived(theme.isDark ? 'Switch to light mode' : 'Switch to dark mode')
</script>

<button type="button" onclick={() => theme.toggle()} class="theme-toggle" aria-label={label}>
	{#if theme.isDark}
		<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
			<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
		</svg>
	{:else}
		<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
			<circle cx="12" cy="12" r="5" />
			<line x1="12" y1="1" x2="12" y2="3" />
			<line x1="12" y1="21" x2="12" y2="23" />
			<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
			<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
			<line x1="1" y1="12" x2="3" y2="12" />
			<line x1="21" y1="12" x2="23" y2="12" />
			<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
			<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
		</svg>
	{/if}
</button>

<style>
	.theme-toggle {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 44px;
		height: 44px;
		padding: 0;
		border: none;
		border-radius: 50%;
		background-color: var(--color-surface);
		color: var(--color-foreground);
		cursor: pointer;
		transition:
			background-color 0.2s,
			transform 0.2s;
	}

	.theme-toggle:hover {
		background-color: var(--color-surface-hover);
		transform: scale(1.05);
	}

	.theme-toggle:focus-visible {
		outline: 2px solid var(--color-border-focus);
		outline-offset: 2px;
	}

	.theme-toggle svg {
		width: 22px;
		height: 22px;
	}
</style>

Three-Way Selector

<!-- src/lib/theme/ThemeSelector.svelte -->
<script lang="ts">
	import { getThemeContext } from './theme-context.svelte.js'
	import type { ThemePreference } from './types.js'

	const theme = getThemeContext()

	const options: { value: ThemePreference; label: string; icon: string }[] = [
		{ value: 'light', label: 'Light', icon: '☀️' },
		{ value: 'dark', label: 'Dark', icon: '🌙' },
		{ value: 'system', label: 'System', icon: '💻' }
	]
</script>

<div class="theme-selector" role="radiogroup" aria-label="Theme selection">
	{#each options as option (option.value)}
		<button
			type="button"
			role="radio"
			aria-checked={theme.preference === option.value}
			class="option"
			class:selected={theme.preference === option.value}
			onclick={() => theme.setPreference(option.value)}
		>
			<span class="icon">{option.icon}</span>
			<span class="label">{option.label}</span>
		</button>
	{/each}
</div>

{#if theme.isSystem}
	<p class="system-note">
		Currently using <strong>{theme.mode}</strong> based on system settings.
	</p>
{/if}

<style>
	.theme-selector {
		display: inline-flex;
		gap: 4px;
		padding: 4px;
		border-radius: 12px;
		background: var(--color-surface);
	}

	.option {
		display: flex;
		align-items: center;
		gap: 6px;
		padding: 8px 12px;
		border: none;
		border-radius: 8px;
		background: transparent;
		color: var(--color-foreground);
		font-size: 14px;
		cursor: pointer;
		transition: background-color 0.2s;
	}

	.option:hover:not(.selected) {
		background: var(--color-surface-hover);
	}

	.option.selected {
		background: var(--color-background);
		box-shadow: 0 1px 3px var(--color-shadow);
	}

	.icon {
		font-size: 16px;
	}

	.system-note {
		margin-top: 8px;
		font-size: 12px;
		color: var(--color-foreground-muted);
	}
</style>

Using Themes in Components

Most components should just use CSS variables—no context import needed:

<!-- src/lib/components/Card.svelte -->
<script lang="ts">
	import type { Snippet } from 'svelte'

	interface Props {
		title: string
		children: Snippet
	}

	let { title, children }: Props = $props()
</script>

<article class="card">
	<h2>{title}</h2>
	<div class="content">{@render children()}</div>
</article>

<style>
	.card {
		background-color: var(--color-surface);
		border: 1px solid var(--color-border);
		border-radius: 12px;
		padding: 1.5rem;
		transition:
			background-color 0.3s,
			border-color 0.3s;
	}

	.card:hover {
		background-color: var(--color-surface-hover);
	}

	h2 {
		margin: 0 0 1rem;
		color: var(--color-foreground);
	}

	.content {
		color: var(--color-foreground-muted);
	}
</style>

This component automatically adapts to any theme without importing context.

Method 2: Context for Programmatic Access

Only use context when you need programmatic access (canvas, SVG, third-party libraries):

<!-- src/lib/components/Chart.svelte -->
<script lang="ts">
	import { getThemeContext } from '$lib/theme/theme-context.svelte.js'

	const theme = getThemeContext()

	let { data }: { data: number[] } = $props()
	let canvas = $state<HTMLCanvasElement | null>(null)

	$effect(() => {
		if (!canvas) return
		const ctx = canvas.getContext('2d')
		if (!ctx) return

		// Read CSS variables for colors
		const styles = getComputedStyle(document.documentElement)
		const surface = styles.getPropertyValue('--color-surface').trim()
		const primary = styles.getPropertyValue('--color-primary').trim()

		ctx.fillStyle = surface
		ctx.fillRect(0, 0, canvas.width, canvas.height)

		ctx.strokeStyle = primary
		ctx.lineWidth = 2
		ctx.beginPath()
		data.forEach((point, i) => {
			const x = (i / data.length) * canvas.width
			const y = canvas.height - (point / 100) * canvas.height
			if (i === 0) ctx.moveTo(x, y)
			else ctx.lineTo(x, y)
		})
		ctx.stroke()
	})
</script>

<canvas bind:this={canvas} width="400" height="200"></canvas>

Common Mistakes

1. Setting CSS properties via JavaScript

In 95% of cases, setting CSS properties via JavaScript is unnecessary and inefficient. You will see when to use for these 5% it in next article.

// Don't do this - let CSS handle it
root.style.setProperty('--color-background', colors.background)
root.style.setProperty('--color-surface', colors.surface)
// ... 15 more lines

Just set the attribute directly:

// CSS selectors handle the rest
document.documentElement.dataset.theme = mode

2. Importing context in every component

You rarely need programmatic access to theme data.

<script>
	// Don't do this everywhere
	const theme = getThemeContext()
	const bg = theme.isDark ? '#0f172a' : '#ffffff'
</script>

Use CSS variables instead:

<style>
	.card {
		background: var(--color-surface);
	}
</style>

3. Duplicating colors in TypeScript

Don’t maintain two sources of truth.

export const darkColors = {
	background: '#0f172a'
	// ... same colors as CSS
}

CSS should be your single source of truth.

:root[data-theme='dark'] {
	--color-background: #0f172a;
}

Project Structure

src/
├── lib/
│   └── theme/
│       ├── types.ts                  # TypeScript interfaces (minimal)
│       ├── theme-context.svelte.ts   # Context management
│       ├── ThemeProvider.svelte      # Provider component
│       ├── ThemeToggle.svelte        # Toggle button
│       ├── ThemeSelector.svelte      # Three-way selector
│       └── index.ts                  # Public exports
├── routes/
│   └── +layout.svelte                # Wraps app in ThemeProvider
├── hooks.server.ts                   # Optional: Cookie-based SSR
├── app.html                          # Flash prevention script
└── app.css                           # ALL theme colors defined here

Performance Considerations

The CSS-first approach is highly performant:

  1. Native browser optimization — CSS variable switching is handled internally by the browser, not JavaScript
  2. No JavaScript loops — We don’t iterate over colors or call setProperty
  3. Single reflow — Setting data-theme triggers one style recalculation
  4. DevTools friendly — Easy to inspect and debug in Styles panel

Most components should use CSS variables exclusively. Reserve getThemeContext() for components that genuinely need programmatic access.


When NOT to Use Context

Context adds complexity. Consider simpler alternatives:

ScenarioRecommendation
Global theme only (no nested overrides)Use useTheme hook pattern
Static theme (no user preference)Just CSS variables, no JavaScript
Component libraryExpose theming through CSS variables, not context
Micro-frontendsUse shadow DOM or CSS containment

Use Context when you specifically need nested theme overrides—that’s its unique capability.


Conclusion

A production theme system with Context is simpler than it might seem when you follow the CSS-first approach:

  1. CSS is the single source of truth — All colors defined in app.css using :root[data-theme] selectors
  2. JavaScript just sets an attributedocument.documentElement.dataset.theme = mode
  3. Components use CSS variables — No context imports needed for most components
  4. Context enables nested overrides — The main reason to use Context over module-level state

The result is less code, better performance, and easier debugging.


Key Takeaways

  1. CSS handles colors — Define themes with :root[data-theme] selectors, not JavaScript objects.

  2. JavaScript sets one attributedata-theme is all you need. No setProperty loops.

  3. Most components need no context — Just use var(--color-surface) in CSS.

  4. Context is for nested overrides — That’s its unique value over module-level state.

  5. Prevent flash with inline script — Runs before render, sets data-theme immediately.

  6. Getters maintain reactivity — Return get mode() not mode from context.


Further Reading

Official Documentation