Phía trình duyệt của web, trong một mô hình tư duy duy nhất. AI sẽ viết các file này cho bạn — bộ slide này là bản đồ để bạn đọc, đánh giá và sửa chúng.
← / → để chuyển slide · chuyển EN/VI bên dưới
Mọi trang bạn từng dùng — web bán hàng, dashboard, app ngân hàng — đều chia công việc ra ba file này.
Chữ, ảnh, nút bấm và thứ tự của chúng. Bộ khung, cùng những gì nằm bên trong.
Màu, khoảng cách, chỗ đặt từng thứ. Biến xấp ô vuông thô thành một trang được thiết kế.
Điều xảy ra khi bạn click, gõ hay cuộn. Trang không còn là bức tranh tĩnh mà bắt đầu phản hồi.
Frontend là phía client. Việc của server kết thúc ngay khi bàn giao ba loại file — mọi thứ sau đó xảy ra trong trình duyệt của bạn.
Gửi trang dưới dạng file thuần: HTML + CSS + JS.
Phân tích HTML thành một cái cây, áp dụng CSS, chạy JS.
Nhìn thấy các pixel — và click vào chúng. Frontend là mọi thứ nằm giữa server và mắt bạn.
server ──(html · css · js)──▶ browser ──(pixels)──▶ your eyes
Bốn nhịp. Sau nhịp 3 trang đã 'sống' trong trình duyệt của bạn — và nhịp 4 lặp lại ở mỗi lần click.
Trình duyệt của bạn hỏi một server: 'đưa cho tôi trang này'.
Nó gửi HTML + CSS + JS. Toàn bộ câu trả lời chỉ là các file text.
HTML thành một cây phần tử, CSS tô và xếp chúng vào chỗ, JS bắt đầu chạy.
JS phản hồi ngay tại chỗ; cần dữ liệu mới, nó hỏi server lần nữa (một API call) — không cần tải lại.
Bộ slide này là tấm bản đồ đó — nhận ra, đánh giá, sửa.
Các file hoạt động được: HTML có cấu trúc thật, CSS trông chuyên nghiệp, JS biết phản hồi. Bạn hiếm khi tự tay gõ chúng.
Đọc cấu trúc, đánh giá thiết kế, thử hành vi. Bạn cần tấm bản đồ — chứ không phải bằng lái.
Bốn slide ngăn cách tiếp theo chính là bản đồ: HTML, CSS, Bootstrap, JS.
Các thẻ có ý nghĩa, lồng nhau thành một cái cây. File duy nhất quyết định trang web LÀ gì.
Hai nửa: <head> là metadata và link (không thấy), <body> là mọi thứ bạn nhìn thấy.
<!doctype html>
<html>
<head> ← invisible: title, CSS links
<title>My page</title>
<link rel="stylesheet" href="style.css">
</head>
<body> ← everything the user sees
<h1>Hello, world</h1>
<p>This is the first paragraph.</p>
<p id="status"></p>
</body>
</html>
This is the first paragraph.
click vào đây — bạn có thể sửa tôi
Bên phải là bản render trực tiếp của chính trang này — click vào dòng status, bạn có thể sửa nó. Thẻ đi theo cặp: <h1>…</h1>.
HTML gần như không có cú pháp. Giữ ba điều này là bạn đọc được mọi trang.
<div> mở, </div> đóng. Thứ nằm giữa là nội dung của thẻ.
Thẻ nằm trong thẻ, luôn đóng đúng chỗ. Thứ tự lồng chính là cấu trúc của trang.
Thông tin thêm trong thẻ mở: <a href="/shop">. href nói link đi tới đâu.
Bạn không cần học thuộc HTML — cần nhận ra nó khi bạn (hoặc AI của bạn) đọc.
| Thẻ | Nó làm gì |
|---|---|
| h1 – h6 | Tiêu đề, lớn → nhỏ. Mỗi trang một h1, còn lại là phân cấp. |
| p · a · img | Đoạn văn · liên kết · ảnh — ba trụ cột của nội dung. |
| div · span | Ô chứa chung — những ô không mang nghĩa, dùng để gói mọi thứ vào. |
| ul / li | Danh sách gạch đầu dòng và các mục của nó. |
| button · input · form | Bộ ba tương tác: bấm · gõ vào · gửi đi. |
| nav · header · main · footer | Các vùng chuẩn của trang: menu · trên cùng · nội dung · dưới cùng. |
Một logo, một danh sách link, một nút. Chưa có CSS — chỉ có cấu trúc.
<nav>
<a class="logo">MyShop</a>
<ul>
<li><a href="/shop">Shop</a></li>
<li><a href="/about">About</a></li>
</ul>
<button>Log in</button>
</nav>
Bên phải chạy thật — click Shop, About hay Log in để thấy phản hồi. Mọi thanh menu bạn từng thấy đều là phiên bản của <nav> này.
Gần như mọi web app bạn từng dùng đều có đúng hình dạng này: thanh trên cùng, cột bên trái, nội dung ở giữa.
<body>
├── <nav> menu bar — logo, links, 'login'
├── <div class="layout">
│ ├── <aside> sidebar — filters, sub-menu
│ └── <main> content — what the page is about
└── <footer> links, copyright
# every element above is just a <div> or its friends —
# the layout you see is CSS placed on this tree
Đọc cái cây này từ trên xuống và bạn mô tả được cấu trúc của gần như mọi dashboard.
Nhận ra mẫu hình, đừng học thuộc cú pháp. Phần lớn UI 'sang' chỉ là HTML thường khoác thêm CSS.
Một <button>, hoặc <a> được CSS hóa cho giống nút. Một phần tử, một việc.
Một <input type='checkbox'> được CSS hóa thành công tắc — hoặc nút bấm đổi class.
Một input kèm popup do JS hiện ra. Cái lịch thực ra chỉ là lưới các nút nhỏ.
Một hàng <img> trong flexbox; JS quyết định bạn đang xem slide nào.
<img> + <div> + một đường viền. 'Card' là biệt danh cho một ô được style.
Các input nằm trong <form>, thêm một <button type='submit'>. Nút gửi mới là ý chính.
UI phổ biến nhất trên web: một ô để gõ, và một nút để gửi.
<form>
<label>Email</label>
<input type="email" placeholder="you@example.com">
<label>Password</label>
<input type="password">
<button type="submit">Log in</button>
</form>
Bên phải chạy thật — bấm Log in để form phản hồi (JS sẽ chặn ở đây). Mỗi <label> mô tả cho <input> bên cạnh.
#status trỏ đúng một phần tử trên trang. Như số nhà: một địa chỉ một số.
class="card" có thể nằm trên 50 phần tử cùng lúc. Như huy hiệu của đội. Bootstrap hoạt động hoàn toàn bằng những cái này.
CSS và JS dùng đúng những tên này để tìm phần tử — nơi ba file gặp nhau lần đầu.
<div> chẳng nói lên điều gì. <nav>, <main>, <article> nói vùng đó LÀ gì.
Người dùng trình đọc màn hình di chuyển theo điểm mốc: họ nhảy thẳng tới <main> và bỏ qua các menu.
Công cụ tìm kiếm đọc ngữ nghĩa để lập chỉ mục cho trang. HTML ngữ nghĩa chính là SEO miễn phí.
AI đọc markup có ngữ nghĩa tốt hơn hẳn một 'bát canh div' — và chính bạn sau ba tháng nữa cũng vậy.
Mọi card trên web — sản phẩm, hồ sơ, bài viết — đều có hình dạng này: ảnh trên cùng, tiêu đề, chữ, hành động.
<div>
<img src="product.jpg" alt="Espresso machine">
<h3>Espresso Machine</h3>
<p>Pulls a proper shot at home.</p>
<button>Add to cart</button>
</div>
Pulls a proper shot at home.
Bên phải là card chạy thật — bấm Add to cart. Chính markup này sẽ thành Bootstrap card ở phần kế tiếp, chỉ bằng cách thêm class.
Những lỗi mà AI (và người) vẫn thường để lọt. Bạn không cần tìm ra tất cả — chỉ cần nhận ra bốn cái này.
<div>…</span> — cái cây vỡ, và bố cục bên dưới nó loạn lên.
Hai phần tử cùng id="status" — JS lấy cái đầu tiên, nên chữ cập nhật vào sai chỗ.
Trình đọc màn hình không nghe thấy gì. Mỗi <img> cần alt="…" mô tả ảnh.
Nút không có handler, link trỏ tới "" — trang trông như xong mà bấm vào không có gì xảy ra.
CSS trả lời hai câu hỏi: nó trông như thế nào, và nó nằm ở đâu?
Mỗi phần tử là một hình chữ nhật có bốn lớp đồng tâm. Chỉ một mô hình này giải thích phần lớn CSS.
Chính chữ hay ảnh — lớp trong cùng.
Khoảng trống giữa nội dung và viền. Khoảng thở, vẫn nằm trong hộp.
Vạch bao quanh. border-radius (bo góc) cũng nằm ở đây.
Khoảng trống bên ngoài viền — khoảng cách tới các hộp lân cận.
Mỗi luật CSS = một địa chỉ + một danh sách thuộc tính. Ba kiểu địa chỉ bao quát 95% CSS.
button { … } tô kiểu mọi nút trên trang. Thô, nhưng tức thì.
.card { … } trúng những phần tử mang class="card"; #status trúng đúng một phần tử.
Cùng địa chỉ mà JS dùng để tìm phần tử — cái bắt tay đầu tiên giữa ba file.
Một luật CSS = chọn một phần tử, rồi gán cho nó các thuộc tính. Đó là toàn bộ cú pháp.
button {
padding: 10px 18px; /* room inside */
background: #34d399; /* colour */
border: none;
border-radius: 8px; /* rounded corners */
color: white;
font-size: 15px;
}
Bên phải chạy thật — đó là chính CSS trong hộp áp lên nút. Đưa chuột lên nó. AI viết cái này; biết padding / border / radius là cách bạn đọc lại.
Bố cục = quyết định các phần tử nằm ở đâu. CSS có đúng ba công cụ lớn cho việc đó.
Xếp các phần tử thành hàng ngang hoặc cột dọc, chia khoảng cách đều. Thanh menu, hàng card.
Hàng VÀ cột cùng lúc. Dashboard, bức tường ảnh, bố cục cả trang.
'Nếu màn hình hẹp hơn 768px thì làm thế này.' Một codebase, cả điện thoại lẫn máy tính đều chạy.
display: flex xếp các phần tử thành một hàng. Hai trục làm toàn bộ việc — mọi thuộc tính flex khác chỉ là nút vặn trên một trong hai trục này.
justify-content dịch phần tử dọc trục CHÍNH · align-items dịch dọc trục NGANG
Các ô là thật: mỗi hàng là một flex container thực sự mang đúng thuộc tính đó. Đây là thứ trình duyệt tính ra.
space-between: phần tử dính vào hai đầu, chỉ có khoảng trống ở giữa · space-around: mọi phần tử có khoảng trống đều nhau, hai đầu nhận nửa
Ba thuộc tính nữa bao quát phần lớn flex layout thực tế. Cả ba ô dưới đây đều là flex container chạy thật.
items too big → new row
B eats the leftover space
main axis turns vertical
wrap: phần tử dư bắt đầu hàng mới · grow: một phần tử lấp đầy phần còn lại · column: hàng trở thành cột
Trái: chín ô trong lưới 3×3. Phải: các ô trải rộng. Cùng một ý tưởng, ít code hơn hẳn việc chất chồng flexbox.
col-3 của Bootstrap chính là lưới này với 12 cột: col-3 = 3/12 = một phần tư hàng
Media query là một công tắc: cùng phần tử, cách xếp khác nhau theo kích thước màn hình.
/* desktop: sidebar next to content */
.layout { display: flex; }
/* phone: sidebar stacks on top */
@media (max-width: 768px) {
.layout { flex-direction: column; }
}
Bên phải là một cửa sổ chạy thật — kéo slider qua 768px và sidebar xếp chồng. Dưới 768px, cùng trang tự xếp lại; đó là 'responsive'.
Một framework CSS: hàng nghìn kiểu dáng có sẵn, bật bằng tên class — và ba slide kế tiếp chạy nó trực tiếp.
Đó chính là điểm mấu chốt: sự nhất quán miễn phí, để bạn dành thời gian cho trang — chứ không phải pixel.
Bạn tự quyết mọi pixel: padding, màu, viền, trạng thái hover, kích thước trên mobile. Rồi làm lại với mọi nút trên mọi trang.
Thêm một class: class="btn btn-primary". Nút được style sẵn, kèm hover và hành vi mobile. Card, navbar, modal cũng vậy.
HTML giống hệt, cùng một tấm ảnh. Bên phải chỉ thêm tên class — và bỗng trông như được thiết kế.
Pulls a proper shot at home.
Pulls a proper shot at home.
card · card-img-top · card-body · card-title · card-text · btn btn-primary
Mọi thứ bên dưới là Bootstrap 5 thật, được render ngay trong slide này. Mỗi mảnh chỉ là một hoặc hai tên class.
navbar · card · alert · badge · btn — bộ từ vựng của hàng nghìn dashboard
Cùng những input bạn đã thấy ở phần HTML — giờ có thêm class form-*. Trạng thái focus và khoảng cách đã được lo sẵn.
form-label · form-control · form-select · form-check form-switch · btn
Không có khối <style> nào — diện mạo cả trang hoàn toàn đến từ các tên class.
<link href="bootstrap.min.css" rel="stylesheet">
<nav class="navbar">…logo + links…</nav>
<div class="container">
<div class="row">
<div class="col-3">sidebar</div>
<div class="col-9">main content</div>
</div>
<button class="btn btn-primary">Click me</button>
</div>
col-3 / col-9 là lưới: 12 cột, 3+9 lấp đầy một hàng. Đổi số — bố cục đổi theo.
Bạn sẽ không học thuộc Bootstrap — nhưng sáu cái này xuất hiện trong gần như mọi trang làm bằng nó.
| Class | Bạn nhận được gì |
|---|---|
| btn btn-primary | Nút bấm được style — kèm sẵn trạng thái hover và focus |
| container · row · col-* | Lưới 12 cột: col-3 + col-9 lấp đầy một hàng |
| navbar | Thanh menu responsive — tự co gọn trên điện thoại |
| card | Ô có viền cho ảnh + chữ + nút |
| modal | Cửa sổ popup — mở/đóng được lo sẵn |
| form-control | Ô nhập liệu được style |
Mọi bố cục Bootstrap là những hàng gồm 12 ô bằng nhau. Bạn chọn mỗi khối chiếm mấy ô.
<div class="row"> là một kệ chia 12 phần bằng nhau. col-3 lấy 3, col-9 lấy 9 — lấp đầy kệ.
col-6 + col-6 = hai nửa, col-4 × 3 = ba phần. Đổi số và trang tự chảy lại — không cần CSS khác.
col-md-6 nghĩa là 'màn hình medium trở lên thì 6 cột, nhỏ hơn thì xếp chồng'. Cùng một row là cả bố cục điện thoại lẫn desktop.
HTML là bức tranh, CSS là bộ đồ — JS là phản xạ.
Mẫu hình đằng sau mọi tương tác trên web: chờ một sự kiện, rồi thay đổi trang.
const btn = document.querySelector("#save");
btn.addEventListener("click", () => {
document.querySelector("#status")
.textContent = "Saved ✓";
});
Bên phải chạy thật — click Save và virtual console ghi lại toàn bộ sự kiện. Giờ nút đã LÀM được việc thật: click xảy ra, JS sửa trang.
JS đọc và sửa DOM — cái cây phần tử đang sống của trang — nên mọi việc dưới đây đều là 'chỉnh' cái cây đó.
Tab, menu thả xuống, thông báo toast — cùng một phần tử, bật rồi tắt.
Kiểm tra email trông có đúng không trước khi gửi. Hiện lỗi, chặn nút gửi.
Hỏi server lấy JSON — danh sách đơn, giá, bài viết — rồi vẽ chúng vào trang.
Tìm, đọc, sửa, thêm phần tử trên trang. Trang là dữ liệu mà bạn có thể thao tác.
Bạn không cần học thuộc — nhưng khi AI viết chúng, bạn phải nhận ra từng dòng đang làm gì.
| Method | Nó làm gì |
|---|---|
| document.querySelector("#x") | Tìm phần tử đầu tiên theo địa chỉ CSS — cùng tên #x / .x mà CSS dùng. |
| document.querySelectorAll(".x") | Tìm mọi phần tử khớp — một danh sách để lặp. |
| el.textContent = "…" | Gán chữ thuần — an toàn, không parse HTML. |
| el.innerHTML = "…" | Gán markup HTML — trình duyệt parse và render nó. |
| el.addEventListener("click", fn) | Chạy fn khi sự kiện xảy ra. Một phần tử có thể nghe nhiều sự kiện. |
| el.classList.toggle("open") | Thêm class nếu thiếu, bỏ đi nếu có — một công tắc. |
| input.value | Đọc điều người dùng gõ — hoặc gán giá trị mặc định. |
| createElement + append | Tạo phần tử mới từ đầu rồi gắn vào trang. |
Mẫu hình lúc nào cũng vậy: listener → handler → sửa trang. Chỉ tên sự kiện đổi.
Cái kinh điển — nút, link, tab. Xảy ra khi người dùng kích hoạt phần tử.
Xảy ra ở mỗi lần gõ phím. Chìa khóa của ô tìm kiếm trực tiếp và gợi ý tự động — không cần nút 'Tìm'.
Xảy ra khi form được gửi. Gọi e.preventDefault() trong handler để chặn trang tải lại — rồi JS tiếp quản.
Hai nút, một con số. Biến JS là sự thật; trang chỉ là tấm gương của nó.
let count = 0;
const out = document.querySelector("#count");
document.querySelector("#plus")
.addEventListener("click", () => {
count += 1;
out.textContent = count;
});
document.querySelector("#reset")
.addEventListener("click", () => {
count = 0;
out.textContent = count;
});
Bên phải chạy thật — click +1 / Reset; console phản ánh thứ tự 'dữ liệu rồi vẽ'. Thứ tự đó là ý tưởng nền của mọi framework.
Hiện/ẩn gần như luôn chỉ là lật một class CSS. JS không đụng trực tiếp 'display'.
const tabs = document.querySelectorAll(".tab");
tabs.forEach(tab => tab.addEventListener("click", () => {
tabs.forEach(t => t.classList.remove("active"));
tab.classList.add("active");
// CSS: .tab.active shows its panel, the rest hide
}));
Bên phải chạy thật — click một tab; console cho thấy class .active chuyển. Mánh y hệt này đứng sau dropdown, toast, accordion.
Chặn submit, kiểm tra giá trị, hiện hoặc xóa lỗi — không tải lại trang.
const form = document.querySelector("#signup");
form.addEventListener("submit", (e) => {
e.preventDefault(); // don't reload
const email = document.querySelector("#email").value;
const err = document.querySelector("#err");
if (!email.includes("@")) {
err.textContent = "That email looks wrong.";
} else {
err.textContent = ""; // clear it
form.reset();
}
});
Bên phải chạy thật — gửi email sai rồi email đúng; console ghi mỗi nhánh. Đây là bộ khung của mọi thông báo 'email hợp lệ'.
input → lọc dữ liệu → vẽ lại danh sách. Chính mẫu này đứng sau mọi giao diện lọc bạn từng dùng.
const foods = ["Pizza", "Pho", "Sushi", "Banh mi", "Tacos"];
const input = document.querySelector("#search");
const list = document.querySelector("#list");
input.addEventListener("input", () => {
const hits = foods.filter(f =>
f.toLowerCase().includes(input.value.toLowerCase()));
list.innerHTML = hits
.map(f => "<li>" + f + "</li>")
.join("");
});
Bên phải chạy thật — gõ vào ô và console ghi mỗi lần lọc. filter() chọn phần khớp, map() tạo dòng, join() gắn lại.
Mạng hay hỏng: server sập, wifi rớt, route không tồn tại. Khối catch là một phần của tính năng.
async function loadOrders() {
const list = document.querySelector("#orders");
try {
const res = await fetch("/api/orders");
const orders = await res.json();
list.innerHTML = orders
.map(o => "<li>" + o.name + "</li>")
.join("");
} catch (err) {
list.textContent = "Couldn't load orders — retry?";
}
}
Bên phải mô phỏng mạng (không có endpoint thật) — Load orders, hoặc tick Simulate failure để chạm catch. Đánh giá mọi fetch của AI qua khối catch.
Đây là lý do web app vẫn 'sống' khi đang tải — và cũng là lý do lỗi async trông kỳ quái.
fetch bắn ra và JS đi tiếp ngay. Trang vẫn cuộn, vẫn hoạt hình trong khi server đang suy nghĩ.
Các callback xếp hàng: 'khi dữ liệu về, làm điều này.' Thứ tự quan trọng — không thể vẽ dữ liệu chưa về tới.
Giao diện vẫn sống trong lúc chờ. Spinner là cách giao diện thú nhận 'tôi vẫn đang tải'.
'Dữ liệu tôi đi đâu rồi?' thường là 'tôi dùng nó trước khi nó về'. Kỷ luật await/then tồn tại đúng vì điều đó.
Hai mảnh: một nguồn sự thật duy nhất, và một hàm vẽ lại trang từ nguồn đó.
// one source of truth
let state = { open: 3, cart: [] };
// the page is a picture of the state
function render() {
document.querySelector("#open").textContent = state.open;
document.querySelector("#cart").textContent =
"Cart: " + state.cart.length;
}
state.open += 1; // 1. change the data
render(); // 2. repaint the page
Bên phải chạy thật — hai nút đổi state, render() vẽ lại, console ghi lại. Đây chính là thứ React & Vue tự động hóa.
HTML, CSS và JS cùng nói về một phần tử — mỗi file làm việc của mình.
<p id="status"></p> — phần tử tồn tại, vẫn còn trống.
#status { color: red; } — hễ nó có chữ, chữ sẽ màu đỏ.
document.querySelector("#status") được điền chữ ngay khoảnh khắc bạn click nút.
Việc của server kết thúc khi gửi đi. Mọi thứ bạn nhìn thấy và click đều là HTML, CSS và JS đang chạy trong trình duyệt của bạn.
Giờ bạn sẽ để AI sinh các file này — việc của bạn là đọc, đánh giá và sửa chúng.