1. Project Overview & Features
The Smart To-Do List with LocalStorage is an essential frontend project designed to help you bridge the gap between learning JavaScript syntax and building interactive, responsive web applications.
Key Features Implemented:
- Add new tasks with description and priority tag (High, Normal, Low)
- Multi-trigger input: Click Add button or press Enter key
- Mark tasks as completed with strikethrough animation
- Filter by All, Active, and Completed tasks
- Real-time remaining task counter
- Persistent storage with safe sandbox fallback
- Clear completed tasks in one click
2. Step-by-Step Code Walkthrough
A Step 1: Semantic HTML Structure
We structure the interface using clean, semantic HTML elements. This ensures maximum accessibility and provides intuitive hooks for CSS classes and JavaScript query selectors.
<div class="todo-app">
<header class="app-header">
<div class="header-icon"><i class="fas fa-check-double"></i></div>
<div>
<h1>TaskFlow</h1>
<p class="subtitle">Stay organized and get things done</p>
</div>
</header>
<form id="todo-form" class="todo-form" autocomplete="off">
<div class="input-group">
<input type="text" id="todo-input" placeholder="What needs to be done?" required autocomplete="off" spellcheck="false">
<select id="todo-priority" title="Task Priority">
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="low">Low</option>
</select>
<button type="submit" id="add-btn" class="btn-primary" title="Add Task">
<i class="fas fa-plus"></i> <span>Add</span>
</button>
</div>
</form>
<div class="controls-bar">
<div class="filter-tabs">
<button type="button" class="tab-btn active" data-filter="all">All</button>
<button type="button" class="tab-btn" data-filter="active">Active</button>
<button type="button" class="tab-btn" data-filter="completed">Completed</button>
</div>
<button type="button" id="clear-completed-btn" class="btn-clear">
<i class="fas fa-trash-alt me-1"></i> Clear Completed
</button>
</div>
<ul id="todo-list" class="todo-list">
<!-- Tasks dynamically rendered here -->
</ul>
<footer class="app-footer">
<span id="items-left">0 items left</span>
<span class="storage-hint" id="storage-status"><i class="fas fa-database"></i> Storage Ready</span>
</footer>
</div>B Step 2: Styling & Responsive CSS
Modern CSS flexbox and grid layouts are used to make the UI look polished, centered, and responsive across desktop, tablet, and mobile screens.
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
body {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
color: #1e293b;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
overflow-x: hidden;
}
.todo-app {
background: #ffffff;
border-radius: 20px;
width: 100%;
max-width: 520px;
box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.4);
padding: 24px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.app-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
}
.header-icon {
width: 46px;
height: 46px;
background: rgba(16, 185, 129, 0.12);
color: #10b981;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.3rem;
flex-shrink: 0;
}
.app-header h1 {
color: #0f172a;
font-size: 1.5rem;
font-weight: 800;
line-height: 1.1;
}
.subtitle {
color: #64748b;
font-size: 0.85rem;
margin-top: 3px;
}
.todo-form .input-group {
display: flex;
gap: 8px;
margin-bottom: 18px;
}
#todo-input {
flex: 1;
min-width: 0;
padding: 11px 14px;
border: 1.5px solid #cbd5e1;
border-radius: 12px;
font-size: 0.92rem;
outline: none;
transition: all 0.2s;
background: #f8fafc;
}
#todo-input:focus {
background: #ffffff;
border-color: #10b981;
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15);
}
#todo-priority {
padding: 0 10px;
border: 1.5px solid #cbd5e1;
border-radius: 12px;
background: #f8fafc;
color: #334155;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
outline: none;
flex-shrink: 0;
transition: border-color 0.2s;
}
#todo-priority:focus {
border-color: #10b981;
}
.btn-primary {
background: #10b981;
color: #ffffff;
border: none;
padding: 11px 18px;
border-radius: 12px;
font-weight: 700;
font-size: 0.92rem;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
flex-shrink: 0;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
transition: all 0.2s;
}
.btn-primary:hover {
background: #059669;
transform: translateY(-1px);
}
.btn-primary:active {
transform: translateY(0);
}
.controls-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #f1f5f9;
flex-wrap: wrap;
gap: 10px;
}
.filter-tabs {
display: flex;
background: #f1f5f9;
padding: 4px;
border-radius: 10px;
gap: 4px;
}
.tab-btn {
background: transparent;
border: none;
padding: 6px 12px;
font-size: 0.82rem;
font-weight: 700;
color: #64748b;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.tab-btn.active {
background: #ffffff;
color: #0f172a;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
.btn-clear {
background: transparent;
border: none;
color: #ef4444;
font-size: 0.82rem;
cursor: pointer;
font-weight: 600;
transition: color 0.2s;
padding: 4px 6px;
}
.btn-clear:hover {
color: #b91c1c;
text-decoration: underline;
}
.todo-list {
list-style: none;
max-height: 320px;
overflow-y: auto;
margin-bottom: 16px;
padding-right: 2px;
}
.todo-item {
display: flex;
align-items: center;
padding: 10px 12px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
margin-bottom: 8px;
transition: all 0.25s ease;
animation: slideDown 0.25s ease;
gap: 8px;
}
@keyframes slideDown {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
.todo-item:hover {
background: #f1f5f9;
border-color: #cbd5e1;
}
.todo-item.completed {
background: #f8fafc;
opacity: 0.65;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: #94a3b8;
}
.checkbox-custom {
width: 20px;
height: 20px;
border-radius: 6px;
cursor: pointer;
accent-color: #10b981;
flex-shrink: 0;
}
.todo-text {
flex: 1;
min-width: 0;
font-size: 0.92rem;
font-weight: 500;
color: #1e293b;
word-break: break-word;
overflow-wrap: break-word;
}
.priority-tag {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
padding: 2px 7px;
border-radius: 6px;
flex-shrink: 0;
letter-spacing: 0.4px;
}
.priority-high { background: #fee2e2; color: #dc2626; border: 1px solid #fecaca; }
.priority-normal { background: #e0f2fe; color: #0284c7; border: 1px solid #bae6fd; }
.priority-low { background: #f1f5f9; color: #64748b; border: 1px solid #e2e8f0; }
.delete-btn {
background: transparent;
border: none;
color: #94a3b8;
font-size: 0.95rem;
cursor: pointer;
transition: color 0.2s;
padding: 6px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.delete-btn:hover {
color: #ef4444;
}
.empty-state {
text-align: center;
padding: 32px 10px;
color: #94a3b8;
font-size: 0.9rem;
}
.app-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8rem;
color: #64748b;
font-weight: 500;
border-top: 1px solid #f1f5f9;
padding-top: 12px;
flex-wrap: wrap;
gap: 6px;
}
.storage-hint {
display: inline-flex;
align-items: center;
gap: 5px;
color: #10b981;
font-size: 0.76rem;
font-weight: 600;
}
/* Mobile Responsiveness */
@media (max-width: 520px) {
body {
padding: 10px 8px;
align-items: flex-start;
}
.todo-app {
padding: 16px 12px;
border-radius: 16px;
}
.app-header {
gap: 10px;
margin-bottom: 14px;
}
.header-icon {
width: 38px;
height: 38px;
font-size: 1.1rem;
border-radius: 10px;
}
.app-header h1 {
font-size: 1.3rem;
}
.subtitle {
font-size: 0.78rem;
}
.todo-form .input-group {
display: grid;
grid-template-columns: 1fr auto;
gap: 8px;
}
#todo-input {
grid-column: 1 / -1;
width: 100%;
padding: 10px 12px;
font-size: 0.88rem;
}
#todo-priority {
height: 38px;
font-size: 0.82rem;
}
.btn-primary {
height: 38px;
padding: 0 14px;
font-size: 0.85rem;
}
.controls-bar {
flex-direction: column;
align-items: stretch;
gap: 8px;
margin-bottom: 12px;
}
.filter-tabs {
width: 100%;
display: flex;
}
.tab-btn {
flex: 1;
text-align: center;
padding: 6px 4px;
font-size: 0.76rem;
}
.btn-clear {
width: 100%;
text-align: center;
padding: 4px;
font-size: 0.78rem;
}
.todo-item {
padding: 8px 10px;
gap: 6px;
}
.todo-text {
font-size: 0.86rem;
}
.priority-tag {
font-size: 0.65rem;
padding: 2px 5px;
}
.delete-btn {
padding: 4px;
font-size: 0.88rem;
}
.app-footer {
flex-direction: column;
align-items: center;
gap: 4px;
text-align: center;
}
}C Step 3: JavaScript Logic & Event Handling
Here is the complete JavaScript code handling the state, event listeners, mathematical logic, and dynamic DOM rendering:
// Storage Safe Helper
const STORAGE_KEY = 'taskflow_todos_v2';
function loadTodos() {
try {
const data = localStorage.getItem(STORAGE_KEY);
if (data) {
const parsed = JSON.parse(data);
if (Array.isArray(parsed) && parsed.length > 0) return parsed;
}
} catch (e) {
console.warn('LocalStorage unavailable in sandbox, using memory state:', e);
}
return [
{ id: 1, text: 'Learn JavaScript basics & DOM manipulation', priority: 'high', completed: true },
{ id: 2, text: 'Build interactive beginner projects', priority: 'normal', completed: false },
{ id: 3, text: 'Master state management and event listeners', priority: 'low', completed: false }
];
}
function saveTodos(data) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} catch (e) {}
}
let todos = loadTodos();
let currentFilter = 'all';
// DOM Elements
const form = document.getElementById('todo-form');
const input = document.getElementById('todo-input');
const prioritySelect = document.getElementById('todo-priority');
const addBtn = document.getElementById('add-btn');
const list = document.getElementById('todo-list');
const itemsLeft = document.getElementById('items-left');
const filterBtns = document.querySelectorAll('.tab-btn');
const clearCompletedBtn = document.getElementById('clear-completed-btn');
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function renderTodos() {
list.innerHTML = '';
const filtered = todos.filter(t => {
if (currentFilter === 'active') return !t.completed;
if (currentFilter === 'completed') return t.completed;
return true;
});
if (filtered.length === 0) {
list.innerHTML = `
<div class="empty-state">
<i class="far fa-clipboard" style="font-size:2.2rem;margin-bottom:8px;display:block;color:#cbd5e1"></i>
No tasks found in this filter.
</div>
`;
} else {
filtered.forEach(todo => {
const li = document.createElement('li');
li.className = `todo-item ${todo.completed ? 'completed' : ''}`;
li.dataset.id = todo.id;
li.innerHTML = `
<input type="checkbox" class="checkbox-custom" ${todo.completed ? 'checked' : ''} aria-label="Mark task completed">
<span class="todo-text">${escapeHtml(todo.text)}</span>
<span class="priority-tag priority-${todo.priority}">${todo.priority}</span>
<button type="button" class="delete-btn" title="Delete Task"><i class="fas fa-trash-alt"></i></button>
`;
list.appendChild(li);
});
}
// Update active counter
const activeCount = todos.filter(t => !t.completed).length;
itemsLeft.textContent = `${activeCount} item${activeCount === 1 ? '' : 's'} left`;
}
function addTodo() {
const text = input.value.trim();
if (!text) {
input.focus();
return;
}
const newTodo = {
id: Date.now(),
text: text,
priority: prioritySelect.value || 'normal',
completed: false
};
todos.unshift(newTodo);
saveTodos(todos);
renderTodos();
input.value = '';
input.focus();
}
// Add task on Form Submit
form.addEventListener('submit', (e) => {
e.preventDefault();
addTodo();
});
// Add task on direct button click
addBtn.addEventListener('click', (e) => {
e.preventDefault();
addTodo();
});
// Add task on Enter key inside input
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
addTodo();
}
});
// Toggle complete & delete using event delegation
list.addEventListener('click', (e) => {
const item = e.target.closest('.todo-item');
if (!item) return;
const id = Number(item.dataset.id);
if (e.target.classList.contains('checkbox-custom')) {
todos = todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t);
saveTodos(todos);
renderTodos();
} else if (e.target.closest('.delete-btn')) {
todos = todos.filter(t => t.id !== id);
saveTodos(todos);
renderTodos();
}
});
// Filter Tabs
filterBtns.forEach(btn => {
btn.addEventListener('click', () => {
filterBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentFilter = btn.dataset.filter;
renderTodos();
});
});
// Clear Completed
clearCompletedBtn.addEventListener('click', () => {
todos = todos.filter(t => !t.completed);
saveTodos(todos);
renderTodos();
});
// Initial render
renderTodos();3. Core JavaScript Concepts You Learned
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
4. How to Run Locally on Your Computer
- Download the project ZIP: Click the Download Project (.ZIP) button at the top or in the live runner to save
todo-list-project.zip. - Extract the archive: Unzip the downloaded file. Inside you will find separate
index.html,style.css,script.js,index-standalone.html, and a comprehensiveREADME.md. - Open in any browser: Simply double-click
index.html(orindex-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge. - Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click
index.htmland select "Open with Live Server" to enjoy instant live reloading as you modify HTML, CSS, or JS!
Bonus Challenges for Learners
Ready to level up? Try adding these extra features on your own:
- Add sound effects or audio feedback for user button clicks.
- Add custom theme color customizer with CSS variables.
- Export the results to PDF or copy as a formatted text summary.