Bạn yêu cầu agent sửa lại một trang web, và trong một dự án đơn giản, một số file xuất hiện với tên lạ: index.html, style.css, script.js. Hoặc chúng đã tồn tại, nhưng báo cáo của agent ghi "đã đổi style" - và chỉ từ câu đó thì không rõ chính xác nó đã động vào cái gì.

Hãy cùng xem qua ba công nghệ cơ bản mà trình duyệt nhận được: HTML, CSS và JavaScript. Cho dễ hình dung, ta lấy một trang tĩnh đơn giản, không qua bước build. Trong một repository thực tế, các file có thể được đặt tên khác và nằm ở chỗ khác, vì vậy trước khi sửa, nên yêu cầu agent cho xem cấu trúc dự án và tìm các file phụ trách phần tử bạn cần. Bài viết không dạy bạn tự tay dựng giao diện, việc đó đã có agent lo.

Trang web là văn bản mà trình duyệt đọc

Hãy bắt đầu với một trang tĩnh đơn giản: mã nguồn của nó là các file text thông thường. Bạn có thể mở chúng bằng Notepad và đọc bằng mắt. Không cần build thêm gì: trình duyệt đọc HTML, tải các tài nguyên liên quan và dựa vào đó vẽ trang lên màn hình.

Điểm khởi đầu của dự án như vậy thường là file index.html. Trình duyệt mở nó, phân tích markup rồi theo các liên kết tải style, script, hình ảnh và font.

Trình duyệt đọc ba file của một dự án tĩnh đơn giản và hiển thị trang hoàn chỉnhindex.htmlstyle.cssscript.jsTrình duyệtTrang hoàn chỉnh
Đây là cách một dự án tĩnh đơn giản có thể được tổ chức: trình duyệt tải ba file liên quan và hiển thị trang

Trong một dự án có build, mã nguồn có thể nằm trong các component, và HTML, CSS, JavaScript hoàn chỉnh do một công cụ chuyên dụng tạo ra. Các vai trò dưới đây vẫn là một tấm bản đồ hữu ích, nhưng ở đó không cần đi tìm đúng ba file mang những cái tên này.

Ví dụ đơn giản của chúng ta gồm ba file với ba vai trò khác nhau:

  • HTML - trên trang có gì: tiêu đề, đoạn văn, nút, hình ảnh;
  • CSS - trông như thế nào: màu sắc, font, khoảng cách, bo góc, độ mượt;
  • JavaScript - điều gì xảy ra: khi nhấp chuột, khi nhập văn bản, theo hẹn giờ.

Giờ đi lần lượt từng cái.

HTML: markup gồm những gì

HTML là markup, không phải vẽ. Bạn không mô tả cái nút trông ra sao. Bạn nói "ở đây có một nút" - và trình duyệt tự vẽ nó theo cách của mình.

Đơn vị cơ bản của markup là thẻ. Gần như lúc nào thẻ cũng đi thành cặp: thẻ mở và thẻ đóng, nội dung nằm ở giữa.

Dòng button Chơi được tách thành ba phần: thẻ mở, nội dung và thẻ đóng, còn cả khối được ghi chú là phần tử HTML<button>Chơi</button>Phần tử HTMLthẻ mởthẻ đóngnội dung
Trong ví dụ này, ba phần gộp lại tạo thành một phần tử của trang

Mỗi phần có tên riêng, còn tất cả gộp lại được gọi là phần tử. Từ này sẽ còn dùng nhiều: khi nói về trang, người ta hay nói "phần tử", tức là thẻ cùng với những gì nằm bên trong.

Tên thẻ là viết tắt của các từ tiếng Anh: p từ paragraph, a từ anchor, img từ image. Không cần học thuộc, nhưng nhờ vậy thường đoán được một thẻ lạ dùng để làm gì.

Thẻ đóng khác thẻ mở ở dấu gạch chéo. Cũng có những phần tử không có thẻ đóng. Ví dụ, <img> không thể chứa văn bản hay thẻ lồng bên trong, còn địa chỉ hình ảnh thì nó nhận qua thuộc tính src.

Các thẻ lồng vào nhau như hộp trong hộp và được đóng theo thứ tự ngược lại - cái bên trong trước, cái bên ngoài sau. Nếu làm sai thứ tự, trình duyệt không bị lỗi mà sẽ cố đoán. Thường thì đoán không trúng.

Khái niệm thứ hai là thuộc tính. Đó là phần bổ sung nằm bên trong thẻ mở và cho biết thêm điều gì đó về thẻ: liên kết dẫn tới đâu, lấy hình ảnh từ đâu, phần tử này tên là gì.

<a href="https://example.com">Mở trang web</a>
<img src="cover.png" alt="Bìa">
<button id="play" class="btn">Chơi</button>

Thuộc tính class ở dòng cuối sẽ cần đến ngay sau đây: nhờ nó mà style được áp dụng cho nút. Thuộc tính id="play" đặt cho nút một cái tên duy nhất, và script sẽ tìm nút theo tên này.

Giờ là cả một trang. Nó ngắn hơn bạn nghĩ:

<!doctype html>
<html>
<head>
  <title>Trang của tôi</title>
</head>
<body>
  <h1>Trang đầu tiên</h1>
  <p>Một đoạn văn bản bình thường.</p>
  <button id="play" class="btn">Chơi</button>
</body>
</html>

Có hai phần: <head> - phần kỹ thuật, nơi đặt tiêu đề tab và liên kết tới style; <body> - mọi thứ người đọc nhìn thấy trên màn hình. Dòng đầu tiên chỉ báo cho trình duyệt biết đây là HTML hiện đại.

Cấu trúc lồng nhau của trang: bên trong html là head chứa tiêu đề tab và body chứa mọi thứ nhìn thấy trên màn hình<html></html><head></head><body></body><title>Trang của tôi</title><h1>, <p>, <button>, <img>
Hộp trong hộp: head và body nằm bên trong html, và mọi thứ khác nằm bên trong chúng

Sơ đồ cho thấy tính chất chính của markup: mọi thứ trên trang nằm bên trong cái gì đó. <body> bên trong <html>, nút bên trong <body>, văn bản bên trong nút. Cũng vì thế mà thụt lề trong file trông như bậc thang: không phải để cho đẹp, mà để cho thấy cái gì nằm trong cái gì.

Kết luận thực tế rất đơn giản. Tiêu đề tab trình duyệt và phần kết nối style nằm trong <head>, trên màn hình hoàn toàn không thấy chúng. Mọi thứ còn lại - trong <body>. Khi agent ghi trong báo cáo "đã thêm một khối vào body", đó chính là chiếc hộp thứ hai: phần mới xuất hiện trên trang, chứ không nằm trong phần kỹ thuật.

Ngôn ngữ này có hơn một trăm thẻ, nhưng trên một trang thực tế gần như lúc nào cũng chỉ xoay quanh vài thẻ quen thuộc:

ThẻNó làm gì
<h1> ... <h6>tiêu đề, từ cấp chính đến cấp nhỏ nhất
<p>đoạn văn bản
<a>liên kết, địa chỉ nằm trong thuộc tính href
<img>hình ảnh, đường dẫn file nằm trong thuộc tính src
<button>nút
<ul> và <li>danh sách và mục danh sách
<div>khối container, hộp cho các phần tử khác
<span>một mẩu văn bản trong dòng, để tách riêng nó ra

Hai thẻ cuối tự chúng không mang ý nghĩa gì và cũng không vẽ ra gì. Đó là những chiếc hộp rỗng để style và script dễ bám vào, - và chính vì vậy mà trang nào cũng có nhiều chúng nhất.

CSS: trang có được diện mạo như thế nào

Markup trần trông khá nghèo nàn: chữ đen trên nền trắng, một nút hệ thống màu xám, mọi thứ dồn sát lề trái. Phần lớn khác biệt giữa trang như vậy và một trang được trình bày đẹp là do CSS tạo ra.

CSS gồm các quy tắc. Mỗi quy tắc trả lời hai câu hỏi: áp dụng cho cái gì và thay đổi cái gì.

.btn {
  background: #6c5ce7;    /* màu nền */
  color: #ffffff;         /* màu văn bản */
  padding: 12px 24px;     /* khoảng cách bên trong */
  border-radius: 12px;    /* bo góc */
}

Dòng đầu tiên là bộ chọn: nó cho biết quy tắc này áp dụng cho những phần tử nào. Tiếp theo, trong dấu ngoặc nhọn là các cặp "thuộc tính: giá trị", mỗi cặp kết thúc bằng dấu chấm phẩy.

Dấu chấm trước btn nghĩa là class. Còn nhớ thuộc tính class="btn" của nút chứ? Nó phát huy tác dụng ở đây: trong markup, bạn gắn cho phần tử một cái nhãn, còn trong style thì gọi tới nhãn đó. Một nhãn có thể gắn cho mười nút - và chỉ một quy tắc sẽ làm cả mười nút giống hệt nhau.

CSS có hàng trăm thuộc tính, nhưng để đọc file của người khác và hiểu trong đó đang làm gì, chỉ cần vài thuộc tính:

  • color - màu văn bản, background - màu hoặc hình ảnh nền;
  • font-size và font-family - kích thước và kiểu font;
  • padding - khoảng cách bên trong phần tử, margin - bên ngoài, giữa các phần tử cạnh nhau;
  • border-radius - bo góc, cũng là một nửa cảm giác "sang xịn";
  • transition - độ mượt: không đổi phắt một cái, mà chuyển dần trong 0,2 giây.

Thường thì các quy tắc nằm trong một file riêng, còn trang biết đến file đó nhờ một dòng trong phần kỹ thuật:

<head>
  <link rel="stylesheet" href="style.css">
</head>

Xóa dòng này đi - file style sẽ không được tải. Văn bản và các phần tử vẫn còn trong markup, nhưng bố cục và cách trình bày quen thuộc có thể biến mất, và đôi khi trang còn trở nên khó dùng hơn.

Cùng một markup hai lần: bên trái không có style, chỉ có văn bản và một nút chữ nhật màu xám, bên phải vẫn các phần tử đó nhưng có khoảng cách, màu sắc và bo gócChỉ HTMLHTML cộng CSS
Vẫn những phần tử đó, vẫn thứ tự đó. Chỉ màu sắc, khoảng cách và hình dạng thay đổi

Trên sơ đồ, bên trái và bên phải là cùng một trang: tiêu đề, hai dòng văn bản, một nút. Không phần tử nào được thêm vào hay mất đi - trong ví dụ này chỉ có file style được sửa.

Đây là tấm bản đồ để biết tìm ở đâu, chứ không phải bức tường ngăn cách về kỹ thuật. Để có bố cục mới, đôi khi agent phải đổi class hoặc container trong HTML, còn một quy tắc CSS không khéo có thể che mất nút và cản cú nhấp chuột. Vì vậy, sau một lần sửa giao diện, hãy xem diff, mở trang và kiểm tra những thao tác bị ảnh hưởng.

JavaScript: điều gì xảy ra khi có thao tác

HTML và CSS đã có thể hiển thị trang, thêm hiệu ứng chuyển mượt hay animation. Nhưng nút trong ví dụ của chúng ta vẫn chưa có hành động của riêng nó: nhấp vào - điểm số không đổi.

Logic tùy ý trên trang thường do JavaScript thêm vào. Trong HTML, nút đã có một cái tên duy nhất id="play". Giờ thì cách làm rất đơn giản: tìm phần tử, chờ một sự kiện, thay đổi điều gì đó.

// tìm nút bằng id của nó
const button = document.getElementById("play");
let score = 0;

// lắng nghe sự kiện: làm gì khi nhấp
button.addEventListener("click", function () {
  score = score + 1;
  button.textContent = "Điểm: " + score;
});

Lần nhấp đầu đổi dòng chữ thành "Điểm: 1", lần thứ hai thành "Điểm: 2". Nếu không có gì xảy ra, trước hết hãy kiểm tra xem trong HTML có ghi id="play" không, và trong script có dùng đúng tên play đó không.

Ở đây có ba chi tiết. const và let tạo biến - một chiếc hộp có tên, đựng một giá trị. addEventListener cho trang lắng nghe một sự kiện: nhấp chuột, nhập văn bản, rê chuột qua. Còn bên trong dấu ngoặc nhọn là một hàm - đoạn code sẽ chạy khi sự kiện xảy ra.

Để ý dòng cuối: script đổi chữ trên nút. Đây là điểm quan trọng - JavaScript có thể thay đổi cả markup lẫn style ngay trong lúc trang đang chạy. Nó có thể thêm một đoạn văn, ẩn một khối, đổi màu một nút. Vì vậy ranh giới giữa các tầng không phải bức tường, mà là sự phân công.

Trong thực tế, hãy bắt đầu tìm trong JavaScript nếu phần tử phản ứng theo dữ liệu, xử lý cú nhấp hoặc tính toán gì đó. Hiệu ứng hiện dần, đổi màu và chuyển động đơn giản thường do CSS đảm nhận. Đây là điểm xuất phát chứ không phải kết luận cuối cùng: sau khi sửa, hãy mở trang và tự kiểm tra cả giao diện lẫn thao tác - một ảnh chụp màn hình là không đủ.

Một nút trong ba file

Gom tất cả lại trên một phần tử. Nút "Chơi" có mặt cùng lúc trong ba file, và mỗi file lo phần việc của mình.

<button id="play" class="btn">Chơi</button>
.btn {
  background: #6c5ce7;
  border-radius: 12px;
}
const playButton = document.getElementById("play");

playButton.addEventListener("click", function () {
  playButton.textContent = "Trò chơi đã bắt đầu";
});

Nếu các file đã được kết nối, cú nhấp sẽ đổi chữ "Chơi" thành "Trò chơi đã bắt đầu". Giờ thì ví dụ có thể chạy thử được, không cần tưởng tượng ra một hàm không tồn tại.

Và giờ là phần hữu ích nhất - chuyện gì xảy ra nếu lần lượt bỏ từng phần:

  • bỏ HTML - mất tất cả: không còn gì để tạo kiểu hay làm cho sống động;
  • bỏ CSS - nút vẫn ở đó và vẫn chạy, chỉ là trông như cái nút hệ thống màu xám từ thập niên 90;
  • bỏ JS - nút đẹp nhưng chết. Nhấp vào được, nhưng chẳng có gì xảy ra.

Từ đây có một tấm bản đồ đơn giản để giao việc. Nếu bạn không hài lòng với vẻ ngoài, hãy bắt đầu tìm từ style. Nếu không hài lòng với điều xảy ra thì bắt đầu từ script. Nếu phần tử hoàn toàn không có, hãy bắt đầu từ markup. Chỉ là bắt đầu thôi: lần sửa thực tế có thể động đến tầng bên cạnh, vì vậy kết quả của agent cần được kiểm tra qua diff và trong trình duyệt.

Trong thư mục dự án có gì

Bộ file thường gặp nhất cho một trang đơn giản trông như sau:

index.html    markup, điểm vào
style.css     style
script.js     hành vi

Chúng được nối với nhau bằng hai dòng trong markup. Style được kết nối trong phần kỹ thuật, còn script - ở tận cuối, ngay trước thẻ đóng </body>, để lúc script chạy thì mọi phần tử đều đã có sẵn:

<head>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- markup của trang -->
  <script src="script.js"></script>
</body>

Cũng có lúc khác đi. Trong một dự án lớn có hàng chục file, xếp vào các thư mục kiểu css/ và js/, bên cạnh là hình ảnh và font. Cũng có trường hợp ngược lại, khi chỉ có đúng một file - trường hợp này sẽ nói ngay bên dưới. Vai trò của markup, style và logic vẫn giữ nguyên, nhưng tên file và cách kết nối chúng có thể khác.

Khi style và script nằm ngay trong trang

Ba file là cách bố trí phổ biến nhất, nhưng không phải cách duy nhất. Cùng một đoạn CSS có thể đặt ở ba chỗ khác nhau, và sớm muộn gì bạn cũng sẽ gặp cả ba.

Chỗ thứ nhất bạn đã thấy: một file riêng cộng với dòng <link>. Chỗ thứ hai - khối <style> ngay trong markup, không cần file riêng:

<head>
  <style>
    .btn {
      background: #6c5ce7;
      border-radius: 12px;
    }
  </style>
</head>

Bên trong mọi thứ vẫn như cũ: bộ chọn, dấu ngoặc nhọn, các cặp "thuộc tính: giá trị". Chỉ có chỗ đặt là khác. Người ta viết như vậy khi chỉ có một trang và chẳng cần tạo file riêng cho nó.

Chỗ thứ ba là style nội tuyến, tức inline styles. Các thuộc tính gắn thẳng lên phần tử, trong thuộc tính style:

<button class="btn" style="background: #e17055; font-size: 20px">Chơi</button>

Ở đây không cần bộ chọn: đã rõ quy tắc áp dụng cho phần tử nào - chính phần tử mà nó được viết lên. Vì thế không có dấu ngoặc nhọn, không có dấu chấm trước class, chỉ có các cặp "thuộc tính: giá trị" ngăn cách bằng dấu chấm phẩy, tất cả trên một dòng.

Cách này tiện đúng một lần. Sau đó bắt đầu lộ ra nhược điểm. Quy tắc chỉ tác dụng lên đúng một phần tử: mười nút - mười bản sao, và sau này phải sửa cả mười. Một style nội tuyến bình thường nhìn chung được ưu tiên hơn một quy tắc bình thường từ file. Bạn tô màu nút qua style, rồi sửa đúng thuộc tính đó trong style.css và không hiểu vì sao màu không đổi. Vì vậy thuộc tính style là một trong những chỗ cần kiểm tra đầu tiên; các quy tắc ưu tiên chi tiết thì lúc này chưa cần.

Với script cũng vậy, chỉ là có hai chỗ. File riêng được kết nối qua <script src="...">, hoặc có thể viết code ngay giữa hai thẻ:

<script>
  document.getElementById("play").addEventListener("click", function () {
    alert("Trò chơi đã bắt đầu");
  });
</script>

Gom lại thành một bảng nhỏ để tiện tra cứu:

Cái gìNằm ở đâuTrong HTML trông ra sao
stylefile riêng<link rel="stylesheet" href="style.css">
stylekhối trong chính trang<style> ... </style>
stylengay trên phần tửstyle="color: red"
scriptfile riêng<script src="script.js"></script>
scriptkhối trong chính trang<script> ... </script>

Trường hợp cực đoan là khi dồn hết vào một chỗ: markup, khối <style> và khối <script> cùng nằm trong một index.html. Chuyện này chẳng có gì đáng ngại, với một trang nhỏ thì thậm chí còn đơn giản hơn: chỉ một file, mở trong trình duyệt là chạy, không có gì thất lạc dọc đường. Phiền toái chỉ đến về sau, khi trang phình to và phải tìm thứ gì đó trong cả mớ dài dằng dặc này. Lúc đó nội dung thường được tách ra các file, và ta lại có đúng bộ ba quen thuộc.

Trước khi nhờ agent "đổi style", hãy tìm hiểu xem style nằm ở đâu. Nếu dự án chỉ có một index.html, có thể không hề có file style.css, và chỗ cần sửa sẽ nằm ngay trong markup. Nếu phần tử không thay đổi, hãy yêu cầu kiểm tra ba điều: file style cần thiết đã được kết nối chưa, bộ chọn có khớp không và có style nội tuyến hay quy tắc nào mạnh hơn không. Như vậy phạm vi tìm kiếm được thu hẹp mà không phải đoán xem ai là thủ phạm.

Điều này vận hành thế nào khi làm cùng Claude Code

Giờ thì đã rõ chuyện gì diễn ra khi agent bắt tay vào một trang:

  • trong một dự án tĩnh đơn giản, agent có thể tự viết và kết nối HTML, CSS và JavaScript. Ở dự án khác, trước tiên hãy yêu cầu agent cho xem cấu trúc và chỉ ra các file phụ trách phần tử bạn cần;
  • trước khi ghi file, bạn thấy được chính xác những gì là tùy vào cấu hình và chế độ làm việc. Nếu agent đưa đề xuất ra trước, hãy xem qua; sau khi ghi thì trong mọi trường hợp đều nên xem kỹ diff và các file kết quả. Cái tên style.css gợi ý vai trò của file, nhưng chưa đảm bảo được gì;
  • sửa giao diện thì kiểm tra bằng mắt và bằng cách nhấp vào các phần tử bị ảnh hưởng, sửa hành vi thì kiểm tra bằng thao tác thật. Ảnh chụp màn hình sẽ không cho thấy bộ đếm đã thôi đếm;
  • nếu có gì hỏng, hãy mô tả triệu chứng bạn quan sát được, chứ không phải tầng bạn đoán là thủ phạm: "sau khi sửa, nút vẫn hiện nhưng nhấp vào không có tác dụng gì". Khi đó agent sẽ kiểm tra các mối liên kết giữa HTML, CSS và JavaScript.