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.
<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
<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.
<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.
<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>