Bước 1 — HTML: bộ khung
Tạo file index.html với form thêm việc, danh sách hiển thị, và bộ lọc (Tất cả / Chưa xong / Đã xong). Dùng semantic HTML.
// File: index.html
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo App — Dự án Foundation</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="app">
<h1>📝 Todo App</h1>
<form id="todoForm">
<input type="text" id="todoInput" placeholder="Thêm việc cần làm..." required>
<button type="submit">Thêm</button>
</form>
<div class="filters">
<button class="active" data-filter="all">Tất cả</button>
<button data-filter="active">Chưa xong</button>
<button data-filter="done">Đã xong</button>
</div>
<ul id="todoList"></ul>
<p class="count"><span id="count">0</span> việc còn lại</p>
</main>
<script src="app.js"></script>
</body>
</html>
Bước 2 — CSS: giao diện responsive
Style gọn gàng, có dark mode toggle, card bóng đổ, transition mượt khi thêm/xoá việc.
// File: style.css
* { box-sizing: border-box; margin: 0; }
body {
font-family: "Inter", sans-serif;
background: #f0f2f5;
display: flex;
justify-content: center;
padding: 40px 16px;
}
.app {
background: #fff;
border-radius: 16px;
padding: 32px;
width: 100%;
max-width: 480px;
box-shadow: 0 8px 32px rgba(0,0,0,.08);
}
#todoForm {
display: flex;
gap: 8px;
margin: 20px 0;
}
#todoForm input { flex: 1; padding: 10px 14px; border: 1px solid #dee2e6; border-radius: 8px; }
#todoForm button { padding: 10px 20px; background: #6c5ce7; color: #fff; border: none; border-radius: 8px; cursor: pointer; }
Bước 3 — JavaScript: logic + localStorage
// File: app.js
const STORAGE_KEY = "todos"
function load() {
const raw = localStorage.getItem(STORAGE_KEY)
return raw ? JSON.parse(raw) : []
}
function save(todos) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
}
let todos = load()
let filter = "all"
function render() {
const list = document.querySelector("#todoList")
const filtered = todos.filter(t =>
filter === "all" ? true :
filter === "done" ? t.done : !t.done
)
list.innerHTML = filtered.map(t => `
<li class="${t.done ? "done" : ""}">
<input type="checkbox" ${t.done ? "checked" : ""}
onchange="toggle(${t.id})">
<span>${t.title}</span>
<button onclick="remove(${t.id})">✕</button>
</li>
`).join("")
document.querySelector("#count").textContent = todos.filter(t => !t.done).length
}
document.querySelector("#todoForm").addEventListener("submit", (e) => {
e.preventDefault()
const input = document.querySelector("#todoInput")
if (!input.value.trim()) return
todos.push({ id: Date.now(), title: input.value.trim(), done: false })
input.value = ""
save(todos)
render()
})
Bước 4 — Đẩy lên GitHub Pages
Tạo repo trên GitHub, git init, commit, push. Vào Settings → Pages → chọn branch main. Sau 1 phút bạn có URL thật để gửi cho mọi người.
| Bước | Lệnh / thao tác | Kết quả |
|---|---|---|
| Tạo repo | github.com → New repository | Repo rỗng trên GitHub |
| Đẩy code | git init → add → commit → push | Code lên remote |
| Bật Pages | Settings → Pages → branch main | Có URL https://you.github.io/repo |
| Kiểm thử | Mở URL từ điện thoại | Ai cũng truy cập được |
❓ Dự án web tĩnh đẩy lên GitHub Pages, ai "chạy" server trả về trang web cho người truy cập?
- Hoàn thành Todo App với HTML semantic
- Style responsive với CSS Flexbox
- Logic JS đầy đủ: thêm, xoá, toggle, filter
- Lưu dữ liệu bằng localStorage
- Push lên GitHub và bật GitHub Pages