Xây Dựng Ứng Dụng To-Do List Với JavaScript – Dự Án UI/UX Đẹp, Thực Tế Cho Lập Trình Viên Frontend
Jason Nguyen
Ngày công bố: 03/01/2026
Tác giả
Ngày công bố: 03/01/2026
Xây Dựng Ứng Dụng To-Do List Với JavaScript – Dự Án UI/UX Đẹp, Thực Tế Cho Lập Trình Viên Frontend
Ứng dụng To-Do List (danh sách công việc cần làm) là một trong những dự án kinh điển và thực tế nhất dành cho lập trình viên frontend. Đây không chỉ là bài tập để học JavaScript thuần túy mà còn là cơ hội tuyệt vời để rèn luyện tư duy về UI/UX, xử lý trạng thái, tương tác với DOM, và lưu trữ dữ liệu cục bộ. Bài viết này sẽ hướng dẫn bạn xây dựng một ứng dụng To-Do List hoàn chỉnh với giao diện đẹp mắt, trải nghiệm người dùng mượt mà, và các tính năng thực tế như thêm, sửa, xóa, đánh dấu hoàn thành, lọc và tìm kiếm công việc.
1. Giới thiệu: Tại sao dự án To-Do List lại quan trọng?
Dự án To-Do List là một "cửa sổ" nhìn vào thế giới phát triển frontend thực tế. Nó bao gồm:
- Thao tác DOM: Tạo, cập nhật và xóa các phần tử HTML một cách động.
- Quản lý trạng thái (State Management): Lưu trữ và cập nhật danh sách công việc.
- LocalStorage: Lưu dữ liệu trên trình duyệt để không bị mất khi tải lại trang.
- Xử lý sự kiện: Click, submit, change, v.v.
- UI/UX: Thiết kế giao diện thân thiện, responsive và dễ sử dụng.
Hoàn thành dự án này sẽ giúp bạn tự tin hơn khi bắt tay vào các dự án lớn hơn sử dụng React, Vue, hay Angular.
2. Thiết kế UI/UX trước khi code
Trước khi viết một dòng code nào, hãy phác thảo giao diện và trải nghiệm người dùng. Một ứng dụng To-Do List tốt nên có:
- Input để thêm công việc mới: Có placeholder rõ ràng và nút "Thêm".
- Danh sách công việc: Hiển thị rõ ràng các công việc, có checkbox để đánh dấu hoàn thành, nút sửa và xóa.
- Bộ lọc: Cho phép lọc theo trạng thái (Tất cả, Đang làm, Hoàn thành).
- Thanh tìm kiếm: Tìm kiếm công việc theo tên.
- Thống kê: Hiển thị tổng số công việc và số công việc đã hoàn thành.
- Responsive: Giao diện đẹp trên cả máy tính và điện thoại.
Màu sắc nên dịu nhẹ, dễ chịu, và các nút bấm có hiệu ứng hover, click để tạo cảm giác tương tác.
3. Cấu trúc HTML
Chúng ta sẽ bắt đầu với cấu trúc HTML cơ bản. Sử dụng các thẻ ngữ nghĩa để tổ chức giao diện.
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do List App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>📋 To-Do List</h1>
<!-- Form thêm công việc -->
<form id="todoForm">
<input type="text" id="todoInput" placeholder="Nhập công việc..." required>
<button type="submit" id="addBtn">+ Thêm</button>
</form>
<!-- Bộ lọc và tìm kiếm -->
<div class="filter-section">
<div class="filter-buttons">
<button class="filter-btn active" data-filter="all">Tất cả</button>
<button class="filter-btn" data-filter="active">Đang làm</button>
<button class="filter-btn" data-filter="completed">Hoàn thành</button>
</div>
<input type="text" id="searchInput" placeholder="🔍 Tìm kiếm...">
</div>
<!-- Danh sách công việc -->
<ul id="todoList">
<!-- Các công việc sẽ được render ở đây -->
</ul>
<!-- Thống kê -->
<div class="stats">
<span id="totalTasks">Tổng: 0</span>
<span id="completedTasks">Hoàn thành: 0</span>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
4. CSS – Thiết kế giao diện đẹp mắt
CSS đóng vai trò quan trọng trong trải nghiệm người dùng. Chúng ta sẽ sử dụng CSS hiện đại để tạo giao diện tối giản nhưng đẹp mắt.
/* Reset và biến CSS */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #6C63FF;
--primary-dark: #5A52D5;
--bg: #F0F2F5;
--card-bg: #FFFFFF;
--text: #2D3748;
--text-light: #718096;
--border: #E2E8F0;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
--radius: 12px;
--transition: 0.3s ease;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: var(--bg);
color: var(--text);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 600px;
width: 100%;
background: var(--card-bg);
border-radius: var(--radius);
padding: 30px;
box-shadow: var(--shadow);
}
h1 {
text-align: center;
margin-bottom: 24px;
font-weight: 600;
color: var(--primary);
}
/* Form thêm công việc */
#todoForm {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
#todoInput {
flex: 1;
padding: 12px 16px;
border: 2px solid var(--border);
border-radius: var(--radius);
font-size: 16px;
transition: border-color var(--transition);
outline: none;
}
#todoInput:focus {
border-color: var(--primary);
}
#addBtn {
padding: 12px 24px;
background: var(--primary);
color: white;
border: none;
border-radius: var(--radius);
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background var(--transition);
}
#addBtn:hover {
background: var(--primary-dark);
transform: translateY(-1px);
}
/* Bộ lọc và tìm kiếm */
.filter-section {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 20px;
justify-content: space-between;
align-items: center;
}
.filter-buttons {
display: flex;
gap: 8px;
}
.filter-btn {
padding: 8px 16px;
border: 2px solid var(--border);
background: transparent;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all var(--transition);
}
.filter-btn:hover {
background: var(--bg);
}
.filter-btn.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
#searchInput {
padding: 8px 16px;
border: 2px solid var(--border);
border-radius: 20px;
font-size: 14px;
outline: none;
width: 180px;
transition: border-color var(--transition);
}
#searchInput:focus {
border-color: var(--primary);
}
/* Danh sách công việc */
#todoList {
list-style: none;
margin: 20px 0;
}
.todo-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--bg);
border-radius: var(--radius);
margin-bottom: 8px;
transition: all var(--transition);
animation: slideIn 0.3s ease;
}
.todo-item:hover {
transform: translateX(4px);
box-shadow: var(--shadow);
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: var(--text-light);
}
.todo-item .checkbox {
width: 22px;
height: 22px;
cursor: pointer;
accent-color: var(--primary);
flex-shrink: 0;
}
.todo-item .todo-text {
flex: 1;
font-size: 16px;
word-break: break-word;
}
.todo-item .todo-actions {
display: flex;
gap: 6px;
flex-shrink: 0;
}
.todo-item .edit-btn,
.todo-item .delete-btn {
padding: 4px 10px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all var(--transition);
}
.todo-item .edit-btn {
background: #FFD700;
color: #333;
}
.todo-item .edit-btn:hover {
background: #F5C800;
}
.todo-item .delete-btn {
background: #FF6B6B;
color: white;
}
.todo-item .delete-btn:hover {
background: #E55A5A;
}
/* Animation */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Thống kê */
.stats {
display: flex;
justify-content: space-between;
padding-top: 16px;
border-top: 2px solid var(--border);
font-size: 14px;
color: var(--text-light);
}
.stats span {
font-weight: 500;
}
/* Responsive */
@media (max-width: 500px) {
.container {
padding: 16px;
}
#todoForm {
flex-direction: column;
}
#addBtn {
width: 100%;
}
.filter-section {
flex-direction: column;
align-items: stretch;
}
#searchInput {
width: 100%;
}
.filter-buttons {
justify-content: center;
}
.todo-item {
flex-wrap: wrap;
}
.todo-item .todo-actions {
margin-left: auto;
}
}
5. JavaScript – Xử lý logic và tương tác
Đây là phần quan trọng nhất. Chúng ta sẽ viết JavaScript để:
- Quản lý danh sách công việc trong memory và localStorage.
- Thêm, sửa, xóa, và đánh dấu hoàn thành công việc.
- Lọc và tìm kiếm công việc.
- Cập nhật thống kê.
// Lấy các element DOM
const todoForm = document.getElementById('todoForm');
const todoInput = document.getElementById('todoInput');
const todoList = document.getElementById('todoList');
const filterBtns = document.querySelectorAll('.filter-btn');
const searchInput = document.getElementById('searchInput');
const totalTasks = document.getElementById('totalTasks');
const completedTasks = document.getElementById('completedTasks');
// State
let todos = [];
let currentFilter = 'all';
let currentSearch = '';
// Load dữ liệu từ localStorage
function loadTodos() {
const stored = localStorage.getItem('todos');
if (stored) {
todos = JSON.parse(stored);
}
}
// Lưu dữ liệu vào localStorage
function saveTodos() {
localStorage.setItem('todos', JSON.stringify(todos));
}
// Render danh sách công việc
function render() {
// Lọc và tìm kiếm
let filtered = [...todos];
// Lọc theo trạng thái
if (currentFilter === 'active') {
filtered = filtered.filter(t => !t.completed);
} else if (currentFilter === 'completed') {
filtered = filtered.filter(t => t.completed);
}
// Tìm kiếm theo tên
if (currentSearch.trim()) {
const keyword = currentSearch.toLowerCase().trim();
filtered = filtered.filter(t => t.text.toLowerCase().includes(keyword));
}
// Render danh sách
if (filtered.length === 0) {
todoList.innerHTML = `
<li class="empty-message">
${currentSearch ? 'Không tìm thấy công việc nào' : 'Chưa có công việc nào. Hãy thêm ngay!'}
</li>
`;
} else {
todoList.innerHTML = filtered.map(todo => `
<li class="todo-item ${todo.completed ? 'completed' : ''}" data-id="${todo.id}">
<input type="checkbox" class="checkbox" ${todo.completed ? 'checked' : ''} />
<span class="todo-text">${escapeHTML(todo.text)}</span>
<div class="todo-actions">
<button class="edit-btn">Sửa</button>
<button class="delete-btn">Xóa</button>
</div>
</li>
`).join('');
}
// Cập nhật thống kê
const total = todos.length;
const completed = todos.filter(t => t.completed).length;
totalTasks.textContent = `Tổng: ${total}`;
completedTasks.textContent = `Hoàn thành: ${completed}`;
}
// Escape HTML để tránh XSS
function escapeHTML(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Thêm công việc mới
function addTodo(text) {
const trimmed = text.trim();
if (!trimmed) return;
const newTodo = {
id: Date.now().toString(),
text: trimmed,
completed: false,
createdAt: new Date().toISOString()
};
todos.unshift(newTodo);
saveTodos();
render();
todoInput.value = '';
todoInput.focus();
}
// Xóa công việc
function deleteTodo(id) {
todos = todos.filter(t => t.id !== id);
saveTodos();
render();
}
// Chuyển trạng thái hoàn thành
function toggleTodo(id) {
const todo = todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
saveTodos();
render();
}
}
// Sửa công việc
function editTodo(id) {
const todo = todos.find(t => t.id === id);
if (!todo) return;
const newText = prompt('Sửa công việc:', todo.text);
if (newText !== null && newText.trim()) {
todo.text = newText.trim();
saveTodos();
render();
}
}
// Xử lý sự kiện click trên danh sách
todoList.addEventListener('click', (e) => {
const item = e.target.closest('.todo-item');
if (!item) return;
const id = item.dataset.id;
if (e.target.classList.contains('checkbox')) {
toggleTodo(id);
} else if (e.target.classList.contains('delete-btn')) {
if (confirm('Bạn có chắc muốn xóa công việc này?')) {
deleteTodo(id);
}
} else if (e.target.classList.contains('edit-btn')) {
editTodo(id);
}
});
// Thêm công việc khi submit form
todoForm.addEventListener('submit', (e) => {
e.preventDefault();
addTodo(todoInput.value);
});
// Bộ lọc
filterBtns.forEach(btn => {
btn.addEventListener('click', () => {
filterBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentFilter = btn.dataset.filter;
render();
});
});
// Tìm kiếm
searchInput.addEventListener('input', (e) => {
currentSearch = e.target.value;
render();
});
// Keyboard shortcut: Ctrl+Shift+T để focus vào input
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'T') {
e.preventDefault();
todoInput.focus();
}
});
// Khởi tạo ứng dụng
loadTodos();
render();
6. Các tính năng nâng cao (Bonus)
Nếu bạn muốn thử thách bản thân, hãy thêm các tính năng sau:
- Kéo thả (Drag & Drop): Sắp xếp lại thứ tự công việc bằng drag and drop.
- Hạn chót (Deadline): Thêm ngày đến hạn cho công việc, hiển thị cảnh báo khi sắp quá hạn.
- Dark Mode: Thêm chế độ tối để bảo vệ mắt vào buổi tối.
- Export/Import dữ liệu: Cho phép xuất danh sách ra file JSON và import lại.
- Phân loại (Category): Gán nhãn cho công việc và lọc theo nhãn.
Kết luận: Từ dự án nhỏ đến kỹ năng lớn
Dự án To-Do List tưởng chừng đơn giản nhưng lại chứa đựng hầu hết các kỹ năng mà một lập trình viên frontend cần có: DOM manipulation, event handling, state management, localStorage, và UI/UX design. Việc hoàn thành dự án này không chỉ giúp bạn củng cố kiến thức JavaScript mà còn tạo ra một sản phẩm thực tế có thể đưa vào portfolio của mình. Hãy bắt tay vào code ngay hôm nay, và đừng quên thêm những tính năng của riêng bạn để ứng dụng trở nên độc đáo hơn. Chúc bạn thành công!
Bình luận (0)
No comments yet. Be the first to share your thoughts!