Apps Artificial Intelligence CSS DevOps Go JavaScript Laravel Linux MongoDB MySQL PHP Python Rust Svelte Vue

Building a Todo List with Stores in SvelteKit

2 min read .
Building a Todo List with Stores in SvelteKit

Svelte stores provide a simple way to share reactive state across components. In this tutorial, we will build a small Todo List with:

  • Adding tasks
  • Marking tasks complete
  • Editing tasks
  • Deleting tasks
  • Task statistics
  • A live clock

The example uses Svelte + TypeScript and the classic Svelte store APIs.

1. Define the Task Type

Create src/lib/types/task.ts:

export interface Task {
    id: number;
    title: string;
    done: boolean;
}

Each task has:

  • id: a unique identifier.
  • title: the task text.
  • done: whether the task is complete.

2. Create the Task Store

Create src/lib/stores/tasks.ts:

import { writable, readable, derived } from 'svelte/store';
import type { Task } from '$lib/types/task';

export const tasks = writable<Task[]>([]);

export function addTask(title: string) {
    tasks.update((list) => [
        ...list,
        { id: Date.now(), title, done: false }
    ]);
}

export function toggleTask(id: number) {
    tasks.update((list) =>
        list.map((t) =>
            t.id === id ? { ...t, done: !t.done } : t
        )
    );
}

export function removeTask(id: number) {
    tasks.update((list) => list.filter((t) => t.id !== id));
}

export function updateTask(id: number, newTitle: string) {
    tasks.update((list) =>
        list.map((t) =>
            t.id === id ? { ...t, title: newTitle } : t
        )
    );
}

export const now = readable(new Date(), (set) => {
    const interval = setInterval(() => set(new Date()), 1000);
    return () => clearInterval(interval);
});

export const stats = derived(tasks, ($tasks) => {
    const completed = $tasks.filter((t) => t.done).length;
    const pending = $tasks.length - completed;
    return { total: $tasks.length, completed, pending };
});

The exported helpers do the following:

  • addTask adds a task.
  • toggleTask flips its done state.
  • removeTask removes a task by ID.
  • updateTask replaces a task title.
  • now exposes the current time and updates once per second while subscribed.
  • stats derives total, completed, and pending counts from tasks.

3. Build the Todo List UI

Open src/routes/+page.svelte:

<script lang="ts">
	import { tasks, addTask, toggleTask, removeTask, updateTask, now, stats } from '$lib/stores/tasks';
	
	let newTask: string = "";
	let editingId: number | null = null;
	let editingTitle: string = "";
</script>

<h1>Todo List</h1>

<p>Current time: {$now.toLocaleTimeString()}</p>

<input
	bind:value={newTask}
	placeholder="New task..."/>

<button
	on:click={() => {
		if (newTask.trim()) {
			addTask(newTask);
			newTask = "";
		}
	}}>
	Add
</button>

<ul>
	{#each $tasks as task (task.id)}
		<li>
			<input
				type="checkbox"
				checked={task.done}
				on:change={() => toggleTask(task.id)}/>

			{#if editingId === task.id}
				<input bind:value={editingTitle}/>
				<button
					on:click={() => {
						updateTask(task.id, editingTitle);
						editingId = null;
					}}>
					Save
				</button>
				<button on:click={() => (editingId = null)}>
					Cancel
				</button>
			{:else}
				<span>{task.title}</span>
				<button
					on:click={() => {
						editingId = task.id;
						editingTitle = task.title;
					}}>
					Edit
				</button>
				<button on:click={() => removeTask(task.id)}>
					Delete
				</button>
			{/if}
		</li>
	{/each}
</ul>

<p>
	Total: {$stats.total} | Completed: {$stats.completed} | Pending: {$stats.pending}
</p>

The page can now add, complete, edit, and remove tasks while automatically reflecting changes from the store.

4. Final Result

The application provides full in-memory CRUD behavior plus two examples of other store types:

  • readable for a value updated by an internal producer.
  • derived for values calculated from another store.

Because the task list is only stored in memory, it resets when the page is reloaded. You can extend the example with localStorage, a SvelteKit server endpoint, or a backend database.

Conclusion

Classic Svelte stores are a compact way to share reactive state:

  • The Task interface keeps the data shape explicit.
  • The writable tasks store owns mutable application state.
  • The now and stats stores demonstrate readable and derived values.
  • Components react automatically to store updates through the $store subscription syntax.

Svelte 5 also provides newer runes-based state patterns, but stores remain useful for existing projects and for shared reactive values that fit the store contract.

Related Posts

chevron-up