Accessible Task Tracker
Build a browser-based task list — add, complete, filter, and persist tasks — that's fully usable by keyboard and screen reader, not just by mouse.
Practices: The browser and the DOM, Forms and events, Arrays and objects, localStorage
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
localStorageand reloads correctly on page refresh, including whenlocalStorageis 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-liveregion, 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
- 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.
- Add a task to an in-memory array and render it, without persistence yet.
- Wire up complete/delete and the filter, still without persistence.
- Add
localStoragepersistence, including a safe fallback for missing or malformed stored data. - 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
localStoragein DevTools and reloading shows an empty list, not a broken page.