TaskFlow is a to-do list app built one milestone at a time to practice DOM manipulation, state management, and persistence from first principles — no frameworks, no build tools, no dependencies. It does the ordinary things a task app should: add, edit, complete, delete, filter, search — and does each of them without cutting a state-management corner.
What makes it work
-
One array as the only source of truth.
The
tasksarray inapp.jsis the sole place task data lives during a session. The DOM is always fully rebuilt from it viarenderTasks()— never patched or edited directly — so the UI can never drift from the data. -
A real EN/FR toggle, not a placeholder.
A
data-i18ndictionary swaps every string client-side — labels, empty states, ARIA text — the same pattern used on Bénin Horizons and Luna Bistro, verified live on the deployed demo.
Design at a glance
- bg · #f4f5f7
- surface · #ffffff
- accent · #2f6f4f
- text · #1f2933
System font stack throughout — zero custom fonts, zero font-loading delay.
Built to last
- Tasks persist across reloads via
localStorage - Two distinct empty states — "no tasks yet" vs. "no results for your search/filter" — never a bare blank list
- Responsive across mobile, tablet, and desktop, with visible focus states on every control
For developers — the full technical breakdown
01. State-driven rendering over direct DOM edits
- Problem
- A to-do list's visible state can silently drift from its real data if list items are edited directly in the DOM instead of through one source of truth.
- Choice
- A single
tasksarray drives every render; the DOM is fully rebuilt from it on every change viarenderTasks(), never patched in place. - Result
- Filtering, searching, and the live stats counts are always consistent with the underlying data — there's no code path where the UI can show something the array doesn't.
02. Event delegation instead of per-task listeners
- Problem
- Tasks are created and destroyed constantly; attaching a fresh listener to every new task row doesn't scale cleanly and risks orphaned handlers.
- Choice
- One delegated listener on the list container handles edit/complete/delete clicks for any row, existing or newly rendered.
- Result
- New tasks are interactive immediately with no re-binding step, and the full-rebuild-per-render approach never leaks a stale listener.
Accessibility & performance
- Visible
:focus-visiblestates on the language toggle and:focusstates on the form input and search field - Distinct, explicit empty states for "no tasks yet" vs. "no search/filter results"
- System font stack throughout — zero custom fonts, zero font-loading delay
- Responsive layout verified at mobile, tablet, and desktop widths
What I’d do differently
No automated test suite — each milestone was verified manually. Reasonable at this size, but a v2 would benefit from a small unit-test pass specifically on the filter/search derivation logic, since that's the part most likely to regress silently as features are added.
Stack
- HTML5
- CSS3
- Vanilla JavaScript (ES6+)
- localStorage