Skip to content

Search documentation

Find Svelte Patterns and Svelte reference pages.

No results for ""

Try a shorter query or a related Svelte term.

We’re going to demonstrate reactivity by implementing a tri-state checkbox. In your mental model, think about UI and logic as the 2 core pieces we’re workthing with.

# vanilla javascript

For comparison purposes. Let’s start off with vanilla Javascript.

playground
<label>
	<input
		type="checkbox"
		data-state="0"
		onclick={(e) => {
			const STATES = [
				{ text: 'unchecked', checked: false, indeterminate: false },
				{ text: 'checked', checked: true, indeterminate: false },
				{ text: 'indeterminate', checked: false, indeterminate: true },
			]

			const index = (Number(this.dataset.state) + 1) % 3
			this.dataset.state = index
			const currentState = STATES[index]

			this.checked = currentState.checked
			this.indeterminate = currentState.indeterminate
			this.nextSibling.textContent = currentState.text
		}}
	/>
	unchecked
</label>

The only svelte syntax this code uses is the onclick (only because true vanilla doesn’t render in the REPL).

# inlined to component

playground
<script>
	const STATES = [
		{ text: 'unchecked', checked: false, indeterminate: false },
		{ text: 'checked', checked: true, indeterminate: false },
		{ text: 'indeterminate', checked: false, indeterminate: true },
	]

	let index = $state({ current: 0 })
	const { text, checked, indeterminate } = $derived(STATES[index.current])
</script>

<label>
	<input
		type="checkbox"
		data-state={index}
		onclick={() => (index = (index + 1) % 3)}
		{checked}
		{indeterminate}
	/>
	{text}
</label>

Here we take our first step into svelte’s reactivity. onclick is now only responsible for increamenting the index while reactivty ensures the DOM is up to date.

For inlined reactivty, Svelte’s does you a favor and remove the need to use a closure. This is the most common way to write reactivity.

playground
<script>
	const STATES = [
		{ text: 'unchecked', checked: false, indeterminate: false },
		{ text: 'checked', checked: true, indeterminate: false },
		{ text: 'indeterminate', checked: false, indeterminate: true },
	]

	let index = $state(0)
	const { text, checked, indeterminate } = $derived(STATES[index])
</script>

<label>
	<input
		type="checkbox"
		data-state={index}
		onclick={() => (index = (index + 1) % 3)}
		{checked}
		{indeterminate}
	/>
	{text}
</label>

# detached

The reactive logic is seperated from the UI can can be reused.

playground
<script>
	class Tristate {
		#STATES = [
			{ text: 'unchecked', checked: false, indeterminate: false },
			{ text: 'checked', checked: true, indeterminate: false },
			{ text: 'indeterminate', checked: false, indeterminate: true },
		]
		#index = $state(0)
		#current = $derived(STATES[index])

		get current() {
			return this.#current
		}

		next() {
			this.#index = (this.#index + 1) % 3
		}
	}

	const tristate = new Tristate()
	const { text, checked, indeterminate } = $derived(tristate)
</script>

<label>
	<input
		type="checkbox"
		data-state={index}
		onclick={() => (index = (index + 1) % 3)}
		{checked}
		{indeterminate}
	/>
	{text}
</label>

# @attach