Scenario

Build a single-page task list: a form to add a task, a list of current tasks, a way to mark a task complete, and a way to filter the list (all / active / completed, say). Tasks should survive a page reload.

Accessibility isn’t a stretch goal here — it’s a core requirement, on equal footing with the feature working at all. A task list a mouse user can operate but a keyboard user can’t is not a finished task list.

Requirements

Core

  • A form adds a new task from typed text; submitting with an empty or whitespace-only value shows a visible error message instead of adding a blank task.
  • Each task can be marked complete (and un-marked) and deleted.
  • A filter control shows all tasks, only active tasks, or only completed tasks.
  • The full task list — including completion state — persists to localStorage and reloads correctly on page refresh, including when localStorage is empty or contains malformed data.
  • Task state lives in one array of objects ({ id, text, completed }, or similar) that both the render logic and the storage logic read from — not scattered across separate DOM queries.

Accessibility (required, not optional)

  • Every control (the add-task input, the complete checkbox per task, delete buttons, filter buttons) is reachable and operable with the keyboard alone — try unplugging your mouse and using only Tab, Shift+Tab, Enter, and Space.
  • The add-task input has a visible, programmatically associated <label> — not just a placeholder.
  • Adding a task, or an input error, is announced to a screen reader without requiring the user to find and focus a specific element (an aria-live region, or moving focus deliberately, are both valid approaches).
  • Completed tasks are distinguishable by more than color alone (strikethrough text plus a “(done)” visually-hidden label, for example).
  • Focus is never silently lost — deleting a task shouldn’t leave focus stranded on a removed element.

Stretch

  • Editing a task’s text in place.
  • Reordering tasks (keyboard-operable, not drag-only).
  • A due date per task, with overdue tasks visually and programmatically distinguished.

Reflect

  • Where did you have to think about accessibility from the start, versus retrofit it after the feature worked for a mouse user? What would you do differently starting the next project?
  • What happens if two browser tabs have this page open at once and both write to localStorage? Is that a problem worth solving here?

Suggested milestones

  1. Static HTML: the form, an empty list container, and filter buttons — no JavaScript yet. Confirm the form and buttons are keyboard-reachable using only the browser’s default behavior.
  2. Add a task to an in-memory array and render it, without persistence yet.
  3. Wire up complete/delete and the filter, still without persistence.
  4. Add localStorage persistence, including a safe fallback for missing or malformed stored data.
  5. Do an accessibility pass: keyboard-only run-through, checking labels, and checking the announced feedback on add/error.

Acceptance checks

  • Adding a task with an empty input shows an error and does not add a task; the error is announced, not just displayed visually.
  • Marking a task complete, reloading the page, and checking the filter all agree with each other.
  • Every interactive element can be reached and activated without a mouse.
  • Clearing localStorage in DevTools and reloading shows an empty list, not a broken page.