Project: a to-do list with JavaScript
A real to-do list: add, mark as done, delete and save in the browser with localStorage. The code runs right here.
What you will build
A to-do list that saves in your browser: you write a task, tick it when done and it stays there even if you close the tab. It combines the DOM, events, an array of objects and localStorage.
This is not a simulation: it is the page's real JavaScript, running. Add tasks and reload the page to see them persist.
Step 1: the minimal HTML
A form to type into, an empty list and a place for the counter. Nothing else.
<form id="todoForm">
<input type="text" id="todoInput" placeholder="Nueva tarea…">
<button type="submit">Añadir</button>
</form>
<ul id="todoList"></ul>
<p id="todoCount"></p>
Step 2: store the tasks in an array
Each task is an object { texto, hecha }. Together, an array. The array is the "source of truth": the screen just draws it.
let tareas = [];
document.getElementById("todoForm").addEventListener("submit", (e) => {
e.preventDefault(); // no recargar la página
const input = document.getElementById("todoInput");
const texto = input.value.trim();
if (!texto) return;
tareas.push({ texto: texto, hecha: false });
input.value = "";
render();
});
Step 3: draw the list (render)
We clear the list and rebuild it from the array. Each task produces an <li> with its checkbox and its delete button.
function render() {
const ul = document.getElementById("todoList");
ul.innerHTML = "";
tareas.forEach((t, i) => {
const li = document.createElement("li");
if (t.hecha) li.classList.add("is-done");
const chk = document.createElement("input");
chk.type = "checkbox";
chk.checked = t.hecha;
chk.addEventListener("change", () => {
tareas[i].hecha = chk.checked;
render();
});
const span = document.createElement("span");
span.textContent = t.texto;
const del = document.createElement("button");
del.textContent = "✕";
del.addEventListener("click", () => {
tareas.splice(i, 1);
render();
});
li.append(chk, span, del);
ul.appendChild(li);
});
const pendientes = tareas.filter(t => !t.hecha).length;
document.getElementById("todoCount").textContent = "Quedan " + pendientes;
}
textContent and not innerHTML for the task text: that way, if someone types <script>, it shows as text and does not run.Step 4: don't lose it (localStorage)
localStorage only stores text, so we convert the array to JSON when saving and back when loading.
function guardar() {
localStorage.setItem("tareas", JSON.stringify(tareas));
}
// al arrancar la página:
tareas = JSON.parse(localStorage.getItem("tareas")) || [];
render();
// y llamamos a guardar() después de cada cambio (añadir, marcar, borrar)
Where to go next
- Edit a task with a double click.
- Filters: all / pending / done.
- Drag to reorder.
- Swap
localStoragefor a real API with fetch.