- Danh sách HTML nên được chọn theo ngữ nghĩa và cách tương tác, thay vì hình thức hiển thị, và được chia thành danh sách điều khiển, có thứ tự, mô tả, menu và không thứ tự
- Với lựa chọn cố định, dùng
<select>/<option>; với nhập liệu gợi ý, dùng <datalist>, đồng thời cần phân biệt cách hoạt động của multiple, optgroup, size, value
<ol> dùng cho quy trình, sự kiện, hay chuỗi liên tục mà nếu đổi thứ tự thì ý nghĩa sẽ thay đổi; reversed chỉ đảo số thứ tự chứ không đổi thứ tự thực của các mục
<dl> trong HTML5 đã mở rộng từ danh sách định nghĩa thành danh sách mô tả, phù hợp với các cặp khóa-giá trị như thuật ngữ-giá trị, metadata, hay debug JSON
<menu> dùng cho danh sách lệnh như các nút công cụ, khác nav về ngữ nghĩa và nội dung được phép chứa; các danh sách thông thường còn lại do <ul> đảm nhiệm
Danh sách điều khiển: <select>/<option> và <datalist>
- Ngay trong form, bạn cũng có thể tạo các danh sách để người dùng tương tác
-
Các lựa chọn cố định là <select> và <option>
- Khi người dùng chỉ được chọn các mục có sẵn trong danh sách, hãy dùng
<select> và <option>
- Ví dụ danh sách ngôn ngữ sẽ đặt các
<option> như Select a Language, English, French, Spanish, Portuguese bên trong <select name="languages">
<select> mặc định chỉ cho chọn đúng một lựa chọn
- Muốn cho chọn nhiều mục, hãy thêm thuộc tính
multiple
- Khi dùng
multiple, cách hiển thị danh sách sẽ thay đổi, mọi tùy chọn đều hiện ra, và người dùng có thể chọn nhiều mục bằng Shift hoặc Cmd + click
- Khi dùng
select và option thực sự, ngữ nghĩa mặc định của trình duyệt sẽ xử lý việc này mà không cần tự gắn aria-multiselectable vào phần tử danh sách có role="listbox"
-
Nhóm các tùy chọn liên quan bằng <optgroup>
- Nếu muốn nhóm các ngôn ngữ theo họ ngôn ngữ, hãy dùng
<optgroup> để gom nhóm danh sách tùy chọn
- Ví dụ tạo các
<optgroup> có label là Germanic, Romance, Celtic, rồi đặt English, French, Spanish, Portuguese, Irish, Welsh vào từng nhóm
- Nếu muốn một nhóm con nào đó không thể được chọn, hãy thêm thuộc tính
disabled vào <optgroup> tương ứng
- Trong ví dụ, nhóm
Celtic được gắn disabled, làm cho nhóm chứa Irish, Welsh bị vô hiệu hóa
-
Cải thiện bằng tính năng HTML mặc định
- Nếu cần phân tách trực quan giữa các nhóm, bạn có thể dùng
<hr> được phép đặt bên trong <select>
- Thuộc tính
size kiểm soát số lượng mục hiển thị cùng lúc nên hữu ích với danh sách dài
- Khi dùng
size cùng optgroup, các nhãn nhóm cũng sẽ chiếm không gian hiển thị
- Ví dụ dùng
<select name="languages" size="4" multiple> với các nhóm Germanic, Romance, Celtic, Afroasiatic, chèn <hr /> giữa các nhóm, và thêm cả Hebrew, Arabic
Danh sách gợi ý: <datalist>
- Nếu người dùng không bắt buộc phải chỉ chọn từ danh sách mà danh sách chỉ đóng vai trò gợi ý, hãy dùng
<datalist>
<datalist> được liên kết theo hai bước
- Tạo
<datalist> và gán id cho nó
- Đưa giá trị
id đó vào thuộc tính list của <input> tương ứng
- Ví dụ gợi ý ngôn ngữ là đặt các tùy chọn
English, French, Spanish, Portuguese, Irish, Welsh, Hebrew, Arabic trong <datalist id="languages">, rồi nối với ô nhập bằng <input name="language" list="languages">
-
Cách hoạt động của <option value>
- Giá trị mặc định của
<option> là phần văn bản nó bao bọc; nếu có thuộc tính value thì giá trị này sẽ ghi đè mặc định, còn văn bản đóng vai trò như nhãn
- Trong
<select>, đây thường không phải vấn đề lớn vì người dùng chỉ nhìn thấy văn bản; nhưng trong <datalist>, người dùng có thể nhìn nhãn rồi chọn, sau đó ô nhập lại nhận value, dễ gây nhầm lẫn
- Trong ví dụ, nếu chọn
<option value="cy">Welsh</option>, người dùng thấy Welsh nhưng giá trị điền vào ô nhập là cy
- Khi dùng
<datalist>, cần luôn nhớ rằng giá trị được chèn vào là value, không phải nhãn
-
Kết hợp với nhiều kiểu input
<datalist> không chỉ dùng cho các tùy chọn văn bản mà còn có thể kết hợp với các kiểu input khác
- Ví dụ chọn theo tuần là nối
<datalist id="preferred-weeks"> với <input type="week" name="week" id="camp-week" min="2026-W2" max="2026-W51" list="preferred-weeks" />
- Các tuần được gợi ý là
2026-W22, 2026-W23, 2026-W24, 2026-W25
-
Kết hợp với <input type="range">
<datalist> không chỉ hoạt động với chuỗi mà còn với số, nên có thể kết hợp với range input để tạo các mốc có nhãn trên thanh phạm vi
- Ví dụ nhập tỷ lệ tip là nối
<datalist id="recommended-tips"> với <input type="range" name="tips" id="tips" min="0" max="50" step="1" list="recommended-tips" />, và đặt các nhãn 10%, 18%, 30%, 45%
- Trên các trình duyệt họ Chrome, có thể dùng
@supports (x: attr(x type(percentage))) để đọc giá trị label bằng attr(), khai báo giá trị dưới dạng phần trăm bằng type(), rồi đặt các option bằng position: absolute
- Cách tiếp cận cho Firefox dùng
@supports not (x: attr(x type(percentage))), và giá trị được hiển thị bằng ::before
- Cách làm này không đảm bảo mọi trình duyệt sẽ hoạt động giống nhau hoặc hiển thị giống hệt nhau trên màn hình
Danh sách có thứ tự: <ol>
- Với tập hợp các mục cần được đọc theo một thứ tự nhất định, hãy dùng
<ol>
- Tiêu chí không phải là bên cạnh mục có hiển thị số hay không, mà là nếu đổi thứ tự các mục thì ý nghĩa của danh sách có thay đổi hay không
- Các tập hợp phù hợp với
<ol> gồm có thuật toán, chuỗi sự kiện, các mục trên một dải liên tục tăng hoặc giảm, công thức nấu ăn, và danh sách theo thứ tự bảng chữ cái
- Ví dụ công thức bánh mì chuối sẽ biểu diễn bằng
<ol> theo thứ tự: làm nóng lò và chống dính khuôn, trộn nguyên liệu, đổ bột, nướng 60 phút hoặc đến khi xiên tăm ra sạch, rồi để nguội trên giá
- Danh sách nguyên liệu theo bảng chữ cái cũng phù hợp với
<ol> vì nó tuân theo một chuỗi liên tục là bảng chữ cái, với thứ tự baking soda, bananas, brown sugar, butter, eggs, flour, salt
-
Lồng danh sách có thứ tự và không thứ tự
- Một danh sách có thứ tự được cấu trúc tốt phải cho phép người đọc hiểu điều gì xảy ra theo trình tự nào ngay cả khi không nhìn phần render của trình duyệt
- Trong ví dụ công thức, các bước cấp cao dùng
<ol>, các mục bên trong bước mà thứ tự không quan trọng dùng <ul>, và các bước con mà thứ tự lại quan trọng tiếp tục dùng <ol> lồng nhau
- Cấu trúc này giữ nguyên thứ tự cấp cao
Prepare, Mix, Pour, Bake, Cool, trong khi các mục song song bên trong Prepare và Bake dùng <ul>, còn các bước tuần tự trong Mix và Cool dùng <ol>
-
reversed
- Thuộc tính
reversed đổi cách đánh số từ tăng dần sang giảm dần
- Nó không thay đổi thứ tự thực tế của các mục trong danh sách
- Có thể dùng cho danh sách nguyên liệu và số lượng theo kiểu
most to least, từ nhiều đến ít
- Ví dụ đặt
eggs (2), flour (2 cups), bananas (2) (mashed), brown sugar (¾ cup), butter (½ cup), baking soda (1 teaspoon), salt (¼ teaspoon) trong <ol reversed>
-
Đảo thứ tự mục thực sự bằng JavaScript
- Nếu muốn đảo danh sách thật sự, bạn có thể dùng JavaScript để sắp xếp lại các phần tử con
li theo thứ tự ngược và bật/tắt thuộc tính reversed
- Hàm ví dụ biến kết quả
list.querySelectorAll('li') thành mảng, gọi .reverse(), sau đó xóa bằng list.innerHTML = '' rồi gắn lại bằng list.append(...children)
- Cuối cùng gọi
list.toggleAttribute('reversed')
- Sự kiện ví dụ dùng
orderedList.addEventListener('dblclick', (evt) => { reverseList(orderedList) }) để đảo khi double-click
-
start
- Thuộc tính
start dùng để giữ tính liên tục của số thứ tự khi bạn chia một danh sách lớn thành nhiều danh sách có thứ tự thay vì một danh sách duy nhất
- Trong ví dụ công thức bánh mì chuối,
Prepare được để dưới dạng ul, còn Mix là <ol start=2>, Pour là <ol start=5>, Cool là <ol start=7>, nhằm gán số bước nối tiếp cho từng danh sách
- Dù ở giữa có một phần như
6: Bake được biểu diễn bằng danh sách không thứ tự, ol của Cool vẫn có thể bắt đầu bằng start=7 để giữ luồng đánh số của toàn bộ quy trình
Danh sách mô tả: <dl>, <dt>, <dd>
- Danh sách mô tả (description list) là một kiểu danh sách giúp bạn không phải ép mọi nội dung vào
ol hoặc ul
-
Danh sách định nghĩa trong HTML 4
- Trong HTML 4, nó không được gọi là
description list mà là danh sách định nghĩa (definition list), với phạm vi sử dụng hẹp hơn, tập trung vào việc cung cấp định nghĩa
- Cấu trúc gồm
<dt> cho thuật ngữ cần định nghĩa và <dd> cho nội dung định nghĩa; để dùng đúng ngữ nghĩa hơn, thuật ngữ được định nghĩa nên được bọc trong <dfn>
- Ví dụ nối
throw, yeet tới cùng một định nghĩa, còn no cap, bet mỗi từ có định nghĩa riêng
<dl>
<dt><dfn>throw</dfn></dt>
<dt><dfn>yeet</dfn></dt>
<dd>Verb. To discard at a high velocity</dd>
<dt><dfn>no cap</dfn></dt>
<dd>Interjection. Expresses authenticity and truthfulness, sometimes surprise.</dd>
<dt><dfn>bet</dfn></dt>
<dd>Interjection. Expresses agreement and affirmation.</dd>
</dl>
-
Ý nghĩa được mở rộng trong HTML5
- Trong HTML5, nó trở thành danh sách mô tả không còn bị giới hạn ở định nghĩa, và có thể dùng khi bạn có “một tập hợp các thuật ngữ và giá trị”
- HTML5 cho phép dùng
<div> như một wrapper phi ngữ nghĩa để nhóm các <dt> và <dd> có liên quan
- Ví dụ về engine trình duyệt nhóm
Chrome, Opera, Brave, Edge vào Blink-based browsers, và Firefox, Tor, Librewolf vào Gecko-based browsers
<dl>
<div class="dl-item">
<dt>Chrome</dt>
<dt>Opera</dt>
<dt>Brave</dt>
<dt>Edge</dt>
<dd>Blink-based browsers</dd>
</div>
<div class="dl-item">
<dt>Firefox</dt>
<dt>Tor</dt>
<dt>Librewolf</dt>
<dd>Gecko-based browsers</dd>
</div>
</dl>
-
Metadata và debug JSON
- Nếu bạn có một chuỗi các sự thật và nhãn tương ứng, danh sách mô tả rất phù hợp để hiển thị metadata
- Hồ sơ người dùng cũng có thể biểu diễn bằng
<dl>; ví dụ dùng các cặp <dt> và <dd> cho First Frank, Last Taylor, Age 44, Job Writer, Handle Paceaux
- Trong ứng dụng single-page, danh sách mô tả cũng có thể dùng để debug JSON
- Ví dụ
DebugJson duyệt từng cặp key, value của đối tượng bằng Object.entries(obj), render khóa thành <dt><var>...</var></dt> và giá trị thành <dd><code>...</code></dd>
- Nếu giá trị là object và không phải mảng, nó sẽ gọi lại
DebugJson.createDl(value) để tạo <dl> lồng nhau; còn các giá trị khác thì đưa value.toString() vào trong <code>
const debugJson = new DebugJson({foo: 'bar', arr: ['a', 'b'], car: 1}, '.container')
debugJson.render();
- Danh sách mô tả đáp ứng rất rộng cho nhu cầu biểu diễn cặp khóa-giá trị
Menu: <menu>
- Phần tử
menu biểu thị một danh sách lệnh và gần với web có tính tương tác hơn là việc render nội dung
menu phù hợp với danh sách nút thuộc về “công cụ”, chẳng hạn các điều khiển chỉnh sửa văn bản trong một trình soạn thảo rich text
- Ví dụ trình soạn thảo rich text đặt các nút
Strong, Emphasize, Strike vào các li bên trong menu
<menu>
<li><button onclick="strong()">Strong</button></li>
<li><button onclick="emphasize()">Emphasize</button></li>
<li><button onclick="strike()">Strike</button></li>
</menu>
- Theo đặc tả HTML, đây là trường hợp dùng cho toolbar, và ở các trang tương tác, nơi có các nút công cụ thường là ứng viên phù hợp cho
menu
- Ví dụ điều khiển video cũng thuộc
menu, với button dùng commandfor="vid-123" và command="--play", --mute, --fullscreen
<div class="player player--video">
<video source="whatever.mp4" id="vid-123"></video>
<menu>
<li><button commandfor="vid-123" command="--play">Play</button></li>
<li><button commandfor="vid-123" command="--mute">Mute</button></li>
<li><button commandfor="vid-123" command="--fullscreen">Fullscreen</button></li>
</menu>
</div>
Danh sách không thứ tự: <ul>
ul là kiểu danh sách bao trùm cho các nhu cầu danh sách còn lại mà những loại danh sách khác và nav không giải quyết được
- Trong HTML đời cũ, khác biệt giữa danh sách có thứ tự và không thứ tự thiên về hình thức hiển thị như số hay dấu đầu dòng
- Hiện nay, khi xét đến accessibility, screen reader và SEO, trọng tâm phải là thứ tự có mang ý nghĩa hay không, chứ không phải cách hiển thị trực quan
- Danh sách thành viên ban nhạc không có thứ tự quan trọng nên phù hợp với
ul
<h3>Beatles</h3>
<ul>
<li>John Lennon</li>
<li>Paul McCartney</li>
<li>Ringo Star</li>
<li>George Harrison</li>
</ul>
- Danh sách tên các ban nhạc cũng có thể được biểu diễn bằng danh sách không thứ tự
<ul>
<li>Beatles</li>
<li>Rolling Stones</li>
<li>Van Halen</li>
<li>Foo Fighters</li>
</ul>
1 bình luận
Bình luận trên Hacker News
Chỉ cần thử các ví dụ thôi cũng thấy có vẻ datalist hoạt động không tốt trên Mobile Safari
Nếu có vấn đề tương thích ở một thị trường lớn đến mức này thì thực tế có thể xem như hầu như không còn mấy kịch bản đáng để dùng
Đây đúng là kiểu thông tin gáo nước lạnh của thực tế mà tôi không mong muốn nhưng lại rất cần
Hơn 10 năm trước tôi từng làm một dự án dùng widget gợi ý nhập liệu khá mạnh tay trong UI, và khi đó đã dùng plugin jQuery
Đó là phần phức tạp nhất ở frontend, và thực tế cũng là lý do chính để dùng jQuery trong dự án đó
Khi đọc bài này tôi đã nghĩ rằng việc làm lại frontend đó bằng một phiên bản JS tối giản, nhẹ hơn chắc sẽ dễ như ăn cháo, nhưng ngoài đời thì không phải vậy trừ khi bạn có thể chuyển nguyên môi trường của mình sang thiết bị người dùng
Dù vậy, các tính năng hiện nay có trong đặc tả HTML vẫn khá ấn tượng
Từ sau lần đọc XHTML hồi trung học, tôi gần như không theo dõi các thay đổi của đặc tả nữa, chắc thỉnh thoảng cũng nên xem có gì đã đổi
Chỉ là khả năng tương thích trình duyệt thì hồi đó cũng như bây giờ, vẫn rất đau đầu
Ví dụ datalist chắc chắn hoạt động trên iPhone của tôi
Nó được tích hợp vào vùng gợi ý tự động hoàn thành phía trên bàn phím iOS gốc
Tuy vậy không có cách nào để xem hết mọi gợi ý, mà có lẽ đó cũng không phải cách dùng mà datalist hướng tới
Nhưng thuộc tính
disabledcủagroupthì rõ ràng là không hoạt độngNó cũng không hoạt động trên Firefox của Android
Hồi rất lâu trước ở công việc đầu tiên, datalist trên Firefox không hoạt động, nên Firefox đã bị loại khỏi danh sách trình duyệt được hỗ trợ
Đây là tính năng đã có vấn đề trong thời gian rất dài nếu bạn muốn hỗ trợ các trình duyệt ngoài Chrome
Dùng cùng GBoard trên iOS thì hoạt động không tốt
Ví dụ thêm thuộc tính
disabledvàooptgroupđể khiến một số tùy chọn không thể chọn được có vẻ bị lỗi trên Mobile SafariTrên thực tế nó không bị vô hiệu hóa, và các mục bị vô hiệu hóa vẫn có thể được chọn
Trên Safari mới nhất thì đáng ra phải hoạt động, nên không hẳn là bị hỏng mà giống ở trạng thái kỳ quặc hơn
https://caniuse.com/mdn-html_elements_optgroup_disabled
Có vẻ đây có thể là lỗi của Safari
Đây chính là lý do khó dùng HTML gốc cho những thứ vượt quá mức cơ bản
Dù bạn có đọc đủ nhiều và hiểu đủ chắc để viết những bài như thế này, thì trong phần bình luận cuối cùng vẫn sẽ xuất hiện các hành vi lạ, giới hạn và thông tin không hỗ trợ theo từng tổ hợp trình duyệt và thiết bị
Dùng toàn
divcó thể là một lựa chọn đi quá xa về phía ngược lại, nhưng ít nhất các hành vi lạ và giới hạn của nó lại khá nhất quán và dễ thấyVì chúng khớp với thứ do chính tôi viết hoặc do framework tạo ra một cách ổn định hơn
Bài viết thú vị và toàn diện
Đáng tiếc là giờ có nhiều lập trình viên nhảy thẳng vào React mà không học HTML, và giờ lại còn có cả LLM, nên khả năng họ không học HTML luôn là rất lớn
Vì thế ngay cả ở những chỗ chỉ cần HTML đơn giản, họ cũng sẽ đi tìm React component trước
Tôi thấy vậy cũng không sao
Khi tôi lần đầu phải dùng XML, tôi đã phải học đặc tả XML và tự xuất nó ra
Đó là thời kỳ gần như chưa có thư viện tuần tự hóa
Sau này tôi thấy thế hệ đàn em dùng XML, rồi sau đó là JSON làm định dạng trao đổi mà không học nó một cách đầy đủ, nhưng cũng chẳng có chuyện gì nghiêm trọng xảy ra
AJAX cũng đi từ một công nghệ mới nóng hổi sang giai đoạn người ta không còn biết chữ viết tắt đó là gì, và giờ thì đa số còn chẳng nhận ra chính thuật ngữ này
Không phải AJAX đã chết, mà là nó đã trở nên quá phổ biến đến mức không còn cần một từ riêng nữa
Vấn đề của tôi là 20 năm trước tôi đã học HTML rất kỹ, còn sau đó nó thay đổi và cải tiến thế nào thì tôi chỉ biết lắt nhắt một cách tình cờ
CSS thì lại càng như vậy
Thành thật mà nói HTML khá phiền phức
Ví dụ, cách tiếp cận kiểu HTML để style một phần của control là dùng pseudo-class, nhưng selector có thể khác nhau giữa các trình duyệt
Thế thì bạn phải test theo từng trình duyệt vì không thể biết nó có thực sự hoạt động đúng hay không
React không chỉ dễ hơn mà còn đáng tin hơn
Nếu làm bằng React và các
div, bạn có thể kỳ vọng nó sẽ hoạt động giống nhau trên mọi trình duyệtNội dung hay đấy, nhưng đừng kỳ vọng quá nhiều vào
datalistNó thiếu điểm kết nối để dùng thực sự hiệu quả, nên không phù hợp cho thứ gì vượt quá một prototype nhỏ
Tôi tự hỏi liệu HTML linter có thực sự giúp phân biệt những khác biệt kiểu này không
Cũng muốn biết có linter nào có thể ép buộc kiểu lựa chọn thẻ ngữ nghĩa này hay không
Tôi không thật sự hiểu ý tưởng ép buộc thẻ ngữ nghĩa
Trước hết HTML được tạo ra để tác giả dùng một cách sáng tạo, nên tôi không thấy việc ép một thẻ cụ thể thay vì thẻ khác là hợp lý
Khả năng truy cập rất quan trọng, nhưng các ràng buộc hiện đã đủ nhiều rồi
Thứ gần nhất mà tôi biết là https://github.com/kristoff-it/superhtml#diagnostics
SuperHTML không chỉ kiểm tra cú pháp mà còn xác thực cả việc lồng phần tử và giá trị thuộc tính
Không có language server nào khác triển khai toàn bộ đặc tả HTML vào mã kiểm tra như vậy
Hôm nay tôi mới biết điều này
Không hiểu vì sao nhiều framework hơn lại không tận dụng nó
Xét về trải nghiệm người dùng thì nó giống hệt một danh sách thông thường
Nếu nó giúp hiểu code thì cứ dùng cũng được, nhưng trong cây truy cập của trình duyệt và ở mọi khía cạnh khác thì nó chỉ là một danh sách không thứ tự mà thôi
Nếu muốn truyền đạt rằng đây là danh sách các hành động, bạn phải thêm các thuộc tính ARIA
Bài viết có nhắc
role=menu, nhưng chỉ vậy là chưa đủ, mỗi mục cũng cần vai tròmenuitemWAI Authoring Practices Guide giải thích về vai trò và kỳ vọng tương tác, nhưng đừng sao chép nguyên các ví dụ code, và đặc biệt đừng dùng các vai trò này cho menu điều hướng
https://www.w3.org/WAI/ARIA/apg/patterns/menubar/
Người thông minh thì khỏi học HTML khó nhằn, cứ giải quyết bằng vài cái
divHTML ngày nay có nhiều tính năng thú vị
Tôi thích hỏi mọi người xem họ nghĩ một phần tử nào đó làm gì
Chính tôi lúc đầu cũng đoán sai hoàn toàn
Dù đã làm frontend lead vài năm, tôi vẫn thấy có rất nhiều thông tin hữu ích mà mình chưa biết
Có lẽ ở công ty tôi cũng sẽ bắt đầu thử dùng thật
Giá mà các nhà thiết kế thích giao diện datalist mặc định hơn thì tốt biết mấy
Bài blog rất hay, nhưng tôi không tìm được cách xem toàn bộ danh sách bài viết trên blog cùng một lúc
https://blog.frankmtaylor.com/archive không được
https://blog.frankmtaylor.com/archives cũng không phải
https://blog.frankmtaylor.com/posts cũng không được
https://blog.frankmtaylor.com/all cũng không có
https://blog.frankmtaylor.com/blog cũng không phải
Có nhiều người có hơn 10.000 bookmark, nên nếu có một trang danh sách duy nhất chỉ liệt kê tất cả bài đã viết đến nay, không kèm mô tả hay toàn văn, thì sẽ thực sự tiện
Có phải bạn đang nói đến sitemap không
Phần lớn blog đều có
sitemap.xmlliệt kê toàn bộ bài viếtMà tôi cũng tò mò vì sao bạn lại muốn xem hết cả 235 bài viết