Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn bước vào một tòa nhà văn phòng lớn ở Sài Gòn — chẳng hạn Bitexco Financial Tower — nhưng không có bảng chỉ dẫn, không có biển "Sảnh chính", "Thang máy", "Nhà vệ sinh", "Lối thoát hiểm". Bạn vẫn có thể mò mẫm tìm đường, nhưng sẽ mất thời gian, dễ lạc, và cực kỳ mệt mỏi. Với người sáng mắt, ít nhất bạn còn nhìn thấy tổng thể không gian. Nhưng với một người dùng screen reader (trình đọc màn hình), trang web chính là tòa nhà không có biển chỉ dẫn đó — họ nghe từng dòng một, không thấy được bố cục tổng thể.
Landmarks (các "địa danh" hay "vùng mốc") chính là hệ thống biển chỉ dẫn cho trang web. Chúng cho phép người dùng screen reader nhảy thẳng đến khu vực họ cần — menu chính, nội dung chính, ô tìm kiếm, footer — mà không phải nghe lần lượt hàng trăm phần tử. Đây là một trong những kỹ thuật có tỷ lệ "chi phí thấp — lợi ích cao" nhất trong toàn bộ accessibility: chỉ cần dùng đúng vài thẻ HTML, bạn đã tạo ra một trải nghiệm điều hướng khác biệt hoàn toàn.
Ở các bài trước bạn đã học về Semantic HTML nền tảng (Bài 7) và Heading Structure (Bài 17). Landmarks là mảnh ghép còn lại của "bộ khung điều hướng": nếu heading là mục lục chi tiết của một cuốn sách, thì landmarks là các "chương" lớn phân chia toàn bộ cấu trúc trang. Bài này sẽ tập trung sâu vào landmarks và cách kiến trúc tổng thể một trang (page structure) để screen reader hiểu được đâu là đâu.
Khái niệm cốt lõi
Landmark là gì?
Landmark là một vùng (region) trên trang mà screen reader có thể nhận diện và cho phép người dùng nhảy đến trực tiếp. Mỗi landmark có một role (vai trò) ngữ nghĩa — ví dụ "đây là vùng điều hướng", "đây là nội dung chính", "đây là chân trang". Người dùng screen reader có thể mở một danh sách tất cả các landmark trên trang (giống như mở mục lục) và nhảy đến vùng bất kỳ chỉ bằng một phím tắt.
Điều quan trọng bạn cần nắm: role của landmark có thể đến từ hai nguồn:
- Ngầm định (implicit) — thông qua các thẻ HTML5 ngữ nghĩa như
<nav>,<main>,<header>,<footer>. Đây là cách được khuyến nghị. - Tường minh (explicit) — thông qua thuộc tính
role="..."của ARIA. Chỉ dùng khi bạn buộc phải giữ thẻ<div>vì lý do kỹ thuật.
Bảng các landmark HTML gốc
Đây là bảng "học thuộc lòng" quan trọng nhất của bài. Mỗi thẻ HTML5 gốc đã tự động mang một landmark role tương ứng:
| Thẻ HTML | Landmark role ngầm định | Ý nghĩa |
|---|---|---|
<header> (ở cấp cao nhất) | banner | Đầu trang: logo, tiêu đề site, thanh trên cùng |
<nav> | navigation | Nhóm các liên kết điều hướng |
<main> | main | Nội dung chính, duy nhất của trang |
<aside> | complementary | Nội dung phụ trợ: sidebar, quảng cáo, "bài liên quan" |
<footer> (ở cấp cao nhất) | contentinfo | Chân trang: copyright, thông tin liên hệ |
<form> (có nhãn tên) | form | Vùng biểu mẫu |
<section> (có nhãn tên) | region | Vùng nội dung tổng quát cần được đánh dấu |
<header> và <footer> chỉ trở thành landmark (banner / contentinfo) khi chúng nằm ở cấp cao nhất của trang, tức là con trực tiếp của <body>. Nếu bạn đặt <header> bên trong một <article> hay <section>, nó chỉ là đầu mục của phần đó, không phải landmark toàn trang. Tương tự, <form> và <section> chỉ trở thành landmark khi có tên nhận diện (accessible name) — thường qua aria-label hoặc aria-labelledby.Vai trò tương đương trong ARIA
Khi vì lý do nào đó bạn không thể dùng thẻ ngữ nghĩa (ví dụ dự án cũ toàn <div>), bạn có thể "gán" landmark role bằng ARIA:
role="banner"↔<header>role="navigation"↔<nav>role="main"↔<main>role="complementary"↔<aside>role="contentinfo"↔<footer>role="search"— vùng tìm kiếm (không có thẻ HTML gốc tương đương, phải dùng ARIA hoặc thẻ<search>mới)role="region"↔<section>có nhãn
<nav> luôn tốt hơn <div role="navigation">.Vùng search — một trường hợp đặc biệt
Ô tìm kiếm là chức năng cực kỳ phổ biến trên các trang thương mại điện tử Việt Nam như Tiki, Shopee, Sendo. Landmark search giúp người dùng nhảy thẳng đến ô tìm kiếm. Trước đây phải dùng <form role="search">; hiện nay HTML đã có thẻ <search> gốc (được hỗ trợ ở hầu hết trình duyệt hiện đại), tuy nhiên để an toàn với các screen reader cũ, nhiều nhóm vẫn kết hợp <search> với vai trò tường minh hoặc bọc form tìm kiếm trong role="search".
Đặt tên (labeling) khi có nhiều landmark cùng loại
Đây là điểm mà rất nhiều lập trình viên bỏ sót. Một trang thường có nhiều hơn một <nav>: menu chính ở đầu trang, menu phụ ở footer, breadcrumb (đường dẫn phân cấp). Nếu cả ba đều chỉ là <nav> trơn, screen reader sẽ đọc ra "navigation, navigation, navigation" — người dùng không phân biệt được cái nào là cái nào.
Giải pháp: gán nhãn phân biệt bằng aria-label:
<nav aria-label="Menu chính">...</nav>
<nav aria-label="Đường dẫn phân cấp">...</nav>
<nav aria-label="Liên kết chân trang">...</nav>
Lưu ý nhỏ: không nhét từ "navigation" hay "menu" vào aria-label vì screen reader đã tự đọc loại landmark rồi. Nếu bạn viết aria-label="Menu điều hướng", người dùng sẽ nghe "Menu điều hướng, navigation" — thừa. Trong thực tế nhiều team viết tiếng Anh gọn như aria-label="Primary", aria-label="Footer" để tránh lặp.
Tình huống thực tế
Ví dụ 1 — Tiki và bài toán "menu chính vs menu danh mục"
Giả sử đội frontend của Tiki (sàn thương mại điện tử lớn của Việt Nam) rà soát lại accessibility trước một đợt audit. Trang chủ của họ có tới bốn vùng điều hướng: thanh menu tài khoản trên cùng, thanh danh mục sản phẩm ngang, sidebar danh mục dọc bên trái, và menu liên kết ở footer. Trước khi tối ưu, cả bốn đều là <div class="nav"> không có role.
Kết quả: người dùng NVDA (screen reader phổ biến, miễn phí) khi mở danh sách landmark chỉ thấy... trống trơn. Họ buộc phải dùng phím Tab đi qua từng liên kết một — với hơn 40 danh mục sản phẩm, việc tìm được ô "Đăng nhập" mất gần một phút.
Sau khi refactor: đội chuyển <div class="nav"> thành <nav aria-label="..."> với bốn nhãn phân biệt ("Tài khoản", "Danh mục sản phẩm", "Danh mục chi tiết", "Liên kết Tiki"), bọc ô tìm kiếm trong role="search", và đánh dấu vùng sản phẩm chính bằng <main>. Kết quả test lại: người dùng screen reader nhảy đến ô tìm kiếm chỉ bằng một phím tắt, thời gian tìm chức năng giảm từ ~60 giây xuống dưới 5 giây.
Bài học: Landmark không chỉ là "đúng chuẩn" — nó trực tiếp cắt giảm thời gian thao tác. Với sàn TMĐT nơi mỗi giây do dự có thể là một đơn hàng mất đi, đây là khoản đầu tư rất đáng.
Ví dụ 2 — Ngân hàng số và cái bẫy "hai <main>"
Một fintech giả định tên "VietPay" xây dựng dashboard ngân hàng số dạng single-page application (SPA) bằng React. Mỗi màn hình (Trang chủ, Chuyển tiền, Lịch sử giao dịch) là một component riêng, và mỗi component đều cẩn thận bọc nội dung trong <main>. Vấn đề: khi router hiển thị lồng nhau, có thời điểm trên DOM tồn tại hai thẻ <main> cùng lúc.
Theo chuẩn, một trang chỉ được có đúng một landmark main. Khi có hai, screen reader như VoiceOver (trên máy Mac/iPhone) báo cáo mâu thuẫn — có bản đọc cả hai, có bản bỏ qua, khiến người dùng không tin tưởng phím tắt "nhảy đến main content" nữa. Với một ứng dụng ngân hàng, sự thiếu nhất quán này là rủi ro nghiêm trọng: người khiếm thị có thể bỏ lỡ số dư hoặc xác nhận giao dịch.
Đội VietPay sửa bằng cách đặt một <main> cố định ở layout gốc, còn nội dung từng route được render bên trong nó. Họ cũng thêm một liên kết "Bỏ qua đến nội dung chính" (skip link) trỏ tới id của <main> để người dùng bàn phím bỏ qua menu.
Bài học: <main> là duy nhất. Trong SPA, hãy đặt landmark ở layout gốc, đừng để mỗi màn hình con tự sinh ra landmark trùng lặp.
Ví dụ 3 — Trang tin tức VnExpress và landmark "quá nhiều"
Ngược với ví dụ 1, có tình huống thừa landmark. Giả sử một trang tin dạng VnExpress bọc mọi khối nội dung — mỗi bài viết, mỗi box quảng cáo, mỗi widget — trong <section aria-label="...">, biến tất cả thành landmark region. Kết quả: danh sách landmark có tới 30–40 mục, dài hơn cả nội dung cần đọc. Người dùng screen reader mở danh sách landmark ra và... choáng, vì nó mất đi mục đích ban đầu là "điều hướng nhanh".
Nguyên tắc là: landmark dành cho các vùng lớn, có ý nghĩa điều hướng cấp trang — thường một trang chỉ cần 5–8 landmark. Các khối con nên dùng heading (Bài 17) để tổ chức, không phải biến tất cả thành region.
Bài học: Landmark ít mà đúng thì tốt hơn nhiều landmark tràn lan. Chất lượng điều hướng đến từ sự chọn lọc.
Hướng dẫn từng bước
Đây là quy trình bạn có thể áp dụng cho bất kỳ trang nào để thiết lập cấu trúc landmark chuẩn.
Bước 1 — Phác thảo "sơ đồ vùng" của trang. Trước khi viết code, vẽ ra giấy (hoặc trong Figma) các vùng lớn: đầu trang, menu, nội dung chính, sidebar, chân trang. Xác định vùng nào là điều hướng, vùng nào là nội dung.
Bước 2 — Ánh xạ mỗi vùng sang thẻ HTML gốc. Ưu tiên tuyệt đối thẻ ngữ nghĩa:
<body>
<header>...</header> <!-- banner -->
<nav aria-label="Menu chính">...</nav>
<main>
<article>...</article>
<aside aria-label="Bài liên quan">...</aside> <!-- complementary -->
</main>
<footer>...</footer> <!-- contentinfo -->
</body>
Bước 3 — Đảm bảo mọi nội dung nằm trong một landmark. Một quy tắc rất hữu ích: không để "nội dung mồ côi" — mỗi phần tử có ý nghĩa nên nằm trong một landmark nào đó. Nội dung nằm ngoài mọi landmark là vùng mà screen reader khó điều hướng tới.
Bước 4 — Đặt nhãn cho các landmark trùng loại. Rà tất cả <nav>, <aside>, <section>, <form> — nếu có từ hai cái trở lên cùng loại, gán aria-label phân biệt.
Bước 5 — Đánh dấu vùng tìm kiếm. Bọc form tìm kiếm trong role="search" (hoặc thẻ <search>).
Bước 6 — Thêm skip link. Đặt liên kết "Bỏ qua đến nội dung chính" ở đầu trang trỏ tới <main id="main-content">. Đây là cầu nối giữa landmark và điều hướng bàn phím (Bài 11).
Bước 7 — Kiểm tra bằng công cụ. Dùng tiện ích trình duyệt (bạn sẽ học sâu ở Bài 37) như "Landmarks" extension hoặc chế độ landmark của axe DevTools để xem sơ đồ landmark thực tế. Hoặc mở trực tiếp screen reader (Bài 39) và duyệt danh sách landmark.
Lỗi thường gặp & mẹo
Lỗi 1 — Dùng <div> thay cho thẻ ngữ nghĩa. <div class="header"> không tạo landmark nào cả. Đổi sang <header> là xong. Đây là lỗi phổ biến nhất ở các codebase cũ.
Lỗi 2 — Có nhiều hơn một <main>. Chỉ được một main mỗi trang. Kiểm tra kỹ trong SPA và các trang dùng template lồng nhau.
Lỗi 3 — Nhét chữ "navigation"/"menu" vào aria-label. Gây đọc lặp. Nhãn chỉ nên mô tả chức năng của vùng ("Chính", "Chân trang"), không lặp lại loại vùng.
Lỗi 4 — Biến mọi thứ thành region. <section> có aria-label sẽ thành landmark. Đừng lạm dụng; chỉ dùng cho vùng thực sự đáng điều hướng.
Lỗi 5 — Quên đặt tên cho <form> và <section>. Nhắc lại: <form> chỉ thành landmark khi có accessible name; <section> cũng vậy. Không có nhãn thì chúng "vô hình" trong danh sách landmark.
Lỗi 6 — <header>/<footer> lồng bên trong <article>. Chúng sẽ KHÔNG thành banner/contentinfo. Nếu muốn landmark toàn trang, đặt ở cấp con trực tiếp của <body>.
Mẹo hữu ích:
- Một trang lý tưởng thường có 5–8 landmark. Nếu bạn đếm được 20+, khả năng cao đang lạm dụng.
- Luôn có đúng một
banner, mộtmain, mộtcontentinfocho mỗi trang. - Trong review code, hãy tập thói quen mở danh sách landmark của trang trước khi merge — mất 10 giây nhưng bắt được rất nhiều lỗi.
- Landmark và heading bổ trợ cho nhau: landmark chia vùng lớn, heading chia chi tiết bên trong. Đừng bỏ cái nào.
Bài tập thực hành
Bài 1 — Refactor trang tĩnh. Lấy một trang HTML bất kỳ toàn <div> (có thể tự tạo một trang blog đơn giản: đầu trang, menu, một bài viết, sidebar "bài liên quan", chân trang). Chuyển tất cả sang thẻ landmark ngữ nghĩa đúng. Viết ra bảng ánh xạ: thẻ nào → landmark role nào.
Bài 2 — Xử lý trùng lặp. Tạo một trang có ba <nav> (menu chính, breadcrumb, footer). Gán aria-label phân biệt cho từng cái sao cho khi đọc lên không bị lặp từ "navigation".
Bài 3 — Kiểm tra thực tế. Cài một tiện ích landmark cho trình duyệt (ví dụ "Landmark Navigation via Keyboard" hoặc axe DevTools). Mở trang bạn vừa làm ở Bài 1 và Bài 2, chụp lại sơ đồ landmark. Xác nhận: có đúng một banner, một main, một contentinfo, và các navigation đều có nhãn riêng.
Bài 4 — Nâng cao (tùy chọn). Mở một trang thật của một website Việt Nam bạn hay dùng (Tiki, VnExpress, một trang báo...). Dùng tiện ích landmark để audit. Ghi lại: trang đó thiếu landmark nào? Có <main> không? Có bao nhiêu <nav> và chúng có nhãn không? Đề xuất cách sửa.
Tóm tắt
- Landmark là vùng của trang mà screen reader nhận diện được, cho phép người dùng nhảy điều hướng nhanh — như biển chỉ dẫn trong một tòa nhà.
- Ưu tiên thẻ HTML gốc:
<header>(banner),<nav>(navigation),<main>(main),<aside>(complementary),<footer>(contentinfo). Chỉ dùngrole="..."của ARIA khi buộc phải giữ<div>. - Chỉ một
mainmỗi trang; thường có đúng mộtbannervà mộtcontentinfo. - Khi có nhiều landmark cùng loại, gán
aria-labelphân biệt — nhưng đừng lặp lại từ "navigation"/"menu" trong nhãn. <header>/<footer>chỉ là landmark khi ở cấp cao nhất;<form>/<section>chỉ là landmark khi có accessible name.- Đừng lạm dụng — một trang tốt thường chỉ cần 5–8 landmark. Landmark chia vùng lớn, heading chia chi tiết bên trong.
- Đánh dấu vùng tìm kiếm bằng
role="search"và thêm skip link trỏ tới<main>để hoàn thiện bộ khung điều hướng.