Concepts 🚀 Project 🧮 Labs
📚 Learn / Mini Projects / Todo App with localStorage

Todo App with localStorage

Pure frontend — no backend, no server. Add, complete, delete todos. They survive page reload via localStorage.

🚀 Mini Project · 1 files

Project files

📄 index.html html
<!DOCTYPE html>
<html>
<head>
  <title>Todo</title>
  <style>
    body { font-family: sans-serif; max-width: 480px; margin: 2rem auto; }
    .todo { display: flex; gap: 0.5rem; align-items: center; padding: 0.5rem;
            border-bottom: 1px solid #eee; }
    .todo.done span { text-decoration: line-through; color: #999; }
    input[type='text'] { flex: 1; padding: 0.5rem; }
    button { padding: 0.5rem 1rem; cursor: pointer; }
  </style>
</head>
<body>
  <h1>My Todos</h1>
  <form id='form'>
    <input type='text' id='input' placeholder='What to do?' required>
    <button>Add</button>
  </form>
  <div id='list'></div>

  <script>
    // Load todos from localStorage, or start empty
    let todos = JSON.parse(localStorage.getItem('todos') || '[]');

    function save() {
      localStorage.setItem('todos', JSON.stringify(todos));
    }

    function render() {
      const list = document.getElementById('list');
      list.innerHTML = '';
      todos.forEach((t, i) => {
        const div = document.createElement('div');
        div.className = 'todo' + (t.done ? ' done' : '');
        div.innerHTML = `
          <input type='checkbox' ${t.done ? 'checked' : ''}
                 onchange='toggle(${i})'>
          <span>${t.text}</span>
          <button onclick='del(${i})'>x</button>
        `;
        list.appendChild(div);
      });
    }

    function toggle(i) {
      todos[i].done = !todos[i].done;
      save(); render();
    }

    function del(i) {
      todos.splice(i, 1);
      save(); render();
    }

    document.getElementById('form').addEventListener('submit', (e) => {
      e.preventDefault();
      const input = document.getElementById('input');
      todos.push({text: input.value, done: false});
      input.value = '';
      save(); render();
    });

    render();  // initial paint
  </script>
</body>
</html>

How it works

A pure-frontend todo app — no server, no database, no API calls. The state lives in localStorage, so todos survive page reloads. Pattern: (1) Load: JSON.parse(localStorage.getItem('todos') || '[]') — read the saved array, or start with an empty one. (2) save() writes the array back to localStorage on every change. (3) render() rebuilds the DOM from the array. (4) toggle() and del() mutate the array, then save + render. (5) The form's submit listener adds a new todo (use submit, not click, so Enter works). The key insight: the array is the single source of truth; the DOM is just a reflection of it. Every mutation goes: update array → save to localStorage → re-render. This 'state → render' pattern is the foundation of React, Vue, and every modern frontend framework — but you can do it in vanilla JS.

🔍 Where this is used in OpenBenchML The notebook's cell array follows the same pattern: cells = [...]; save to localStorage (planned); render() rebuilds the DOM from the array.