Todo App
A todo list with add, toggle, delete, and filter functionality.
Loading preview…
A classic todo application demonstrating list rendering with repeat(),
computed values for filtering, and signal-based state management.
import { html, signal, computed, repeat } from "https://esm.sh/@elurjs/core@4.0.1";
const todos = signal([
{ id: 1, text: "Learn Elur signals", done: true },
{ id: 2, text: "Build a todo app", done: false },
{ id: 3, text: "Ship to production", done: false },
]);
const filter = signal("all");
const input = signal("");
const filtered = computed(() => {
if (filter.value === "active") return todos.value.filter((t) => !t.done);
if (filter.value === "done") return todos.value.filter((t) => t.done);
return todos.value;
});
const remaining = computed(() => todos.value.filter((t) => !t.done).length);
const add = () => {
const text = input.value.trim();
if (!text) return;
todos.value = [...todos.value, { id: Date.now(), text, done: false }];
input.value = "";
};
const toggle = (id) => {
todos.value = todos.value.map((t) =>
t.id === id ? { ...t, done: !t.done } : t
);
};
const remove = (id) => {
todos.value = todos.value.filter((t) => t.id !== id);
};
html`
<div style="width:100%;max-width:400px">
<div style="display:flex;gap:8px;margin-bottom:12px">
<input
class="search-input"
placeholder="Add a todo..."
value=${() => input.value}
@input=${(e) => input.value = e.target.value}
@keydown=${(e) => { if (e.key === "Enter") add(); }}
/>
<button class="btn-primary" @click=${add}>Add</button>
</div>
<div style="display:flex;gap:4px;margin-bottom:12px">
<button class=${() => `filter-chip${filter.value === "all" ? " active" : ""}`} @click=${() => filter.value = "all"}>All</button>
<button class=${() => `filter-chip${filter.value === "active" ? " active" : ""}`} @click=${() => filter.value = "active"}>Active</button>
<button class=${() => `filter-chip${filter.value === "done" ? " active" : ""}`} @click=${() => filter.value = "done"}>Done</button>
</div>
<ul>
${() => repeat(
filtered.value,
(t) => t.id,
(t) => html`
<li>
<input
type="checkbox"
checked=${() => t.done}
aria-label=${`Mark "${t.text}" as done`}
@change=${() => toggle(t.id)}
/>
<span style=${() => `flex:1;text-decoration:${t.done ? "line-through" : "none"};color:${t.done ? "#8484a0" : "#f0f0f5"}`}>
${t.text}
</span>
<button class="btn-ghost" style="padding:2px 8px;font-size:0.8rem" aria-label=${`Delete "${t.text}"`} @click=${() => remove(t.id)}>✕</button>
</li>
`
)}
</ul>
<p style="color:#a0a0b5;text-align:center;margin-top:12px">
${() => remaining.value} item${() => remaining.value === 1 ? "" : "s"} remaining
</p>
</div>
`.mount("#app");