Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 41 — Designing for Screen Reader Order

Đã đọc 0/13 mục · còn khoảng 12 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn vừa thiết kế một trang sản phẩm đẹp mắt. Trên màn hình, mắt người dùng nhìn thấy: ảnh sản phẩm bên trái, tên và giá bên phải, nút "Mua ngay" nổi bật ở góc. Mọi thứ trông hoàn hảo. Nhưng có một nhóm người dùng không "nhìn" trang của bạn theo cách đó — họ nghe nó. Và với họ, thứ tự mà nội dung được đọc lên có thể hoàn toàn khác với những gì bạn thấy bằng mắt.

Đây chính là một trong những cạm bẫy tinh vi nhất của accessibility mà các designer và developer thường bỏ qua: sự lệch pha giữa thứ tự hiển thị trực quan (visual order) và thứ tự trong mã nguồn (DOM order). Trình đọc màn hình (screen reader) không quan tâm bạn đã dùng CSS để đẩy phần tử này lên trên, kéo phần tử kia sang phải. Nó đọc theo đúng thứ tự bạn viết trong HTML — từ trên xuống dưới, từ trái sang phải trong cây DOM.

Trong các bài trước, chúng ta đã học về screen reader cơ bản (Bài 13), semantic HTML (Bài 7), và heading structure (Bài 17). Bài 41 này tập trung vào một khía cạnh rất cụ thể mà một designer cần nắm: làm thế nào để đảm bảo thứ tự nội dung mà người dùng screen reader trải nghiệm khớp với ý đồ thiết kế của bạn. Đây là kỹ năng nằm ở ranh giới giữa thiết kế và kỹ thuật — nơi một quyết định về CSS Flexbox tưởng chừng vô hại có thể phá vỡ hoàn toàn trải nghiệm của người khiếm thị.

Khái niệm cốt lõi

Screen reader đọc theo DOM order, không phải visual order

Nguyên tắc nền tảng đầu tiên bạn cần khắc cốt ghi tâm: screen reader duyệt nội dung theo thứ tự các phần tử xuất hiện trong DOM (Document Object Model), tức là theo thứ tự bạn viết chúng trong file HTML — trên xuống dưới. Người dùng bàn phím (keyboard navigation) cũng vậy: khi họ nhấn phím Tab, focus di chuyển theo thứ tự DOM (trừ khi bị can thiệp bằng tabindex).

CSS thì khác. CSS được sinh ra để trình bày trực quan. Với Flexbox, Grid, position: absolute, thuộc tính order, float, hay các kỹ thuật transform, bạn có thể đặt bất kỳ phần tử nào ở bất kỳ đâu trên màn hình — bất kể vị trí thật của nó trong mã. Đây là sức mạnh nhưng cũng là con dao hai lưỡi.

Hãy xem một ví dụ tối giản:

<div class="card">
  <p class="price">299.000đ</p>
  <h3 class="name">Áo sơ mi linen</h3>
</div>
.card { display: flex; flex-direction: column; }
.name { order: 1; }
.price { order: 2; }

Trên màn hình, người dùng thấy: "Áo sơ mi linen" rồi mới đến "299.000đ" — hợp lý, tên trước giá sau. Nhưng screen reader lại đọc theo DOM: "299.000đ. Áo sơ mi linen." — giá trước, tên sau. Người khiếm thị nghe một con số bật ra trước khi biết đó là sản phẩm gì. Đây chính là mismatch — sự lệch pha.

Ba thủ phạm phổ biến nhất gây lệch order

Có ba nhóm kỹ thuật CSS thường xuyên tạo ra sự lệch pha này, và bạn cần cảnh giác đặc biệt với chúng:

1. Thuộc tính order trong Flexbox và Grid. Đây là thủ phạm số một. order thay đổi thứ tự hiển thị nhưng KHÔNG thay đổi thứ tự DOM và thứ tự Tab. Một layout dùng order để sắp xếp lại các cột có thể trông hoàn hảo nhưng đọc lên thì lộn xộn.

2. flex-direction: row-reverse / column-reverse. Những giá trị này đảo ngược thứ tự hiển thị của toàn bộ các phần tử con, trong khi DOM vẫn giữ nguyên. Rất nguy hiểm vì nó âm thầm đảo cả thứ tự đọc lẫn thứ tự Tab so với những gì mắt thấy.

3. position: absolute và transform. Khi bạn kéo một phần tử ra khỏi luồng bình thường và đặt nó ở vị trí bất kỳ, mắt thấy nó ở một chỗ nhưng screen reader vẫn đọc nó ở đúng vị trí trong DOM. Ví dụ kinh điển: một banner "Sale" được position: absolute đặt ở đầu trang bằng mắt, nhưng trong DOM lại nằm tận cuối, nên screen reader đọc nó sau cùng.

Tại sao WCAG coi đây là vấn đề nghiêm trọng

Đây không chỉ là chuyện "trải nghiệm hơi khó chịu". WCAG có hẳn một tiêu chí dành riêng: 1.3.2 Meaningful Sequence (Level A) — quy định rằng khi thứ tự trình bày của nội dung ảnh hưởng đến ý nghĩa, thì thứ tự đọc theo chương trình (programmatic reading order) phải xác định đúng được trình tự có nghĩa đó. Nói cách khác: nếu thứ tự mà bạn đọc nội dung làm thay đổi ý nghĩa, thì DOM order buộc phải phản ánh đúng trình tự logic.

Điểm mấu chốt cần hiểu: DOM order phải là "nguồn chân lý" (source of truth) về mặt logic. CSS chỉ nên tinh chỉnh cách hiển thị, chứ không nên tạo ra một câu chuyện hoàn toàn khác so với câu chuyện trong mã nguồn.

Tình huống thực tế

Ví dụ 1: Trang sản phẩm của một sàn TMĐT Việt Nam

Một đội thiết kế tại một sàn thương mại điện tử lớn ở Việt Nam (giả định gọi là ShopViet) làm mới trang chi tiết sản phẩm. Trên desktop, họ muốn khối "đánh giá 4.8 sao (2.341 lượt)" hiển thị ngay dưới tên sản phẩm để tạo niềm tin. Nhưng vì lý do kỹ thuật, khối đánh giá được lập trình nằm cuối card trong HTML, rồi dùng order: -1 để kéo lên đầu về mặt hình ảnh.

Khi một khách hàng khiếm thị dùng screen reader NVDA để mua hàng, trình tự cô ấy nghe được là: tên sản phẩm, giá, mô tả, thông số kỹ thuật, chính sách đổi trả... và mãi đến cuối cùng mới nghe "4.8 sao, 2.341 lượt đánh giá" — đúng lúc cô đã định thoát trang. Yếu tố tạo niềm tin quan trọng nhất bị đẩy ra sau chót.

Bài học rút ra: Nếu một nội dung quan trọng về mặt logic (như đánh giá xây dựng niềm tin), thì nó phải nằm đúng vị trí trong DOM, chứ không chỉ đúng vị trí trên màn hình. Đội ShopViet đã sửa bằng cách di chuyển khối đánh giá lên đúng vị trí trong HTML và bỏ hẳn order: -1. Kết quả: cả người sáng mắt lẫn người dùng screen reader đều gặp thông tin đánh giá ở cùng một điểm trong hành trình.

Ví dụ 2: Layout hai cột "đảo ngược" trên responsive

Một startup fintech ở Singapore thiết kế trang landing với bố cục hai cột: cột trái là hình minh họa, cột phải là đoạn văn giải thích tính năng và nút CTA "Đăng ký ngay". Trên mobile, họ muốn phần văn bản + CTA hiện trước hình minh họa, nên developer dùng flex-direction: column-reverse để đảo thứ tự khi màn hình nhỏ.

Vấn đề: trên desktop, DOM order là [hình, văn bản, CTA]. Trên mobile, mắt thấy [văn bản, CTA, hình] — nhưng khi người dùng bàn phím nhấn Tab, focus lại nhảy tới nút CTA sau khi đã lướt qua hình (theo DOM). Tệ hơn, một người dùng khiếm thị trên mobile nghe screen reader đọc phần hình minh họa (với alt text dài) trước, rồi mới tới nội dung chính. Trải nghiệm giữa hai nhóm người dùng bị tách đôi hoàn toàn — cùng một trang nhưng kể hai câu chuyện khác nhau.

Bài học rút ra: row-reverse và column-reverse cực kỳ nguy hiểm vì chúng làm lệch cả thứ tự đọc lẫn thứ tự Tab so với hình ảnh. Giải pháp đúng: sắp xếp DOM theo thứ tự logic mong muốn (văn bản + CTA trước, hình sau — vì hình chỉ là trang trí), rồi trên desktop dùng Grid với grid-template-areas để đặt hình sang trái mà không cần đảo DOM. Grid cho phép định vị trực quan mà giữ nguyên trật tự mã nguồn logic.

Ví dụ 3: Modal khuyến mãi bị "lạc" trong DOM

Một chuỗi cà phê ở TP.HCM chạy chiến dịch app đặt hàng. Họ thêm một popup khuyến mãi "Giảm 30% đơn đầu tiên" bằng cách chèn thẻ <div> popup ở tận cuối file HTML (ngay trước </body>) rồi dùng position: fixed để hiển thị giữa màn hình. Về mặt hình ảnh, popup che phủ toàn trang ngay khi tải xong.

Nhưng người dùng screen reader không hề biết có popup nào. Focus vẫn ở đầu trang, screen reader vẫn đọc header, menu... trong khi người sáng mắt bị popup chặn lại. Người khiếm thị phải "mò" qua toàn bộ nội dung trang mới tới được popup ở cuối DOM — nếu họ tìm thấy nó.

Bài học rút ra: Với các thành phần nổi (modal, popup) mà được định vị bằng position, bạn không thể chỉ dựa vào vị trí trực quan. Cần chủ động quản lý focus (đưa focus vào modal khi mở) và đảm bảo DOM order hợp lý. Đây là lý do vì sao thứ tự DOM và quản lý focus phải đi đôi với nhau — chủ đề mà Bài 21 về Modal/Dialog Accessibility đào sâu hơn.

Hướng dẫn từng bước

Đây là quy trình một designer nên áp dụng khi thiết kế và bàn giao cho developer để đảm bảo screen reader order đúng:

Bước 1 — Xác định "thứ tự logic" của nội dung trước khi nghĩ đến layout. Trước khi mở Figma và kéo các khối, hãy tự hỏi: "Nếu tôi đọc trang này to lên cho một người không nhìn thấy màn hình, tôi sẽ đọc theo thứ tự nào để họ hiểu?" Viết ra danh sách đánh số 1, 2, 3... Đây chính là DOM order mục tiêu.

Bước 2 — Thiết kế layout trực quan như bình thường, nhưng ghi chú các điểm lệch. Khi bố trí giao diện, nếu bạn cố tình đặt một khối ở vị trí khác với thứ tự logic (ví dụ giá nằm bên phải nhưng logic nên đọc sau tên), hãy đánh dấu điểm đó. Trong Figma, bạn có thể dùng annotation (Bài 40) để ghi rõ "DOM order: đọc tên trước, giá sau".

Bước 3 — Ưu tiên sắp xếp DOM theo thứ tự logic, dùng Grid để định vị trực quan. Nguyên tắc vàng: viết HTML theo thứ tự logic, dùng CSS Grid (grid-template-areas) để đặt chúng ở vị trí trực quan mong muốn. Grid cho phép bạn tách hoàn toàn "thứ tự mã" khỏi "vị trí hiển thị" mà thường không gây lệch order, vì bạn định vị bằng vùng chứ không đảo thứ tự phần tử.

Bước 4 — Tránh order, row-reverse, column-reverse cho nội dung có ý nghĩa. Nếu buộc phải dùng, chỉ dùng cho các phần tử thuần trang trí không mang ý nghĩa và không nhận focus.

Bước 5 — Kiểm tra bằng Tab. Cách nhanh nhất phát hiện lệch order: nhấn Tab liên tục qua trang và quan sát focus có nhảy theo thứ tự trực quan hợp lý không. Nếu focus "nhảy cóc" lung tung, gần như chắc chắn bạn có mismatch.

Bước 6 — Kiểm tra bằng screen reader. Bật NVDA (Windows) hoặc VoiceOver (Mac), dùng chế độ duyệt tuần tự (đọc từ đầu tới cuối) và lắng nghe. Thứ tự đọc có khớp với thứ tự logic bạn viết ở Bước 1 không?

Lỗi thường gặp & mẹo

Lỗi 1: Nghĩ rằng "trông đúng là đủ". Nhiều designer kiểm tra bằng mắt và thấy mọi thứ hoàn hảo. Nhưng mắt không phát hiện được lệch DOM. Luôn kiểm tra bằng Tab và screen reader.

Lỗi 2: Lạm dụng tabindex dương để "sửa" thứ tự. Khi phát hiện Tab nhảy sai, một số người vội dùng tabindex="1", tabindex="2"... để ép thứ tự. Đây là giải pháp tồi: tabindex dương chỉ sửa thứ tự Tab chứ KHÔNG sửa thứ tự đọc của screen reader ở chế độ duyệt, đồng thời rất khó bảo trì. Cách đúng là sửa DOM order.

Lỗi 3: Dùng column-reverse cho responsive mà quên hậu quả. Như ví dụ fintech Singapore đã cho thấy. Mẹo: thay vì đảo hiển thị, hãy sắp lại DOM theo thứ tự mobile (thường là thứ tự logic quan trọng nhất trước), rồi dùng Grid/media query để bố trí lại cho desktop.

Lỗi 4: Quên rằng float và absolute cũng gây lệch. Không chỉ Flexbox. Một sidebar dùng float: right nhưng nằm đầu DOM sẽ được đọc trước nội dung chính.

Mẹo hay: Dùng công cụ như tota11y hoặc tính năng "Reading order" của các extension kiểm thử để trực quan hóa thứ tự DOM ngay trên trang. Trong Chrome DevTools, bạn cũng có thể dùng tab Accessibility để xem cây accessibility tree — nó phản ánh thứ tự mà công nghệ hỗ trợ sẽ trải nghiệm.

Mẹo cho designer làm việc với dev: Khi bàn giao, hãy đính kèm một "reading order spec" — sơ đồ đánh số thứ tự đọc mong muốn. Điều này giúp developer viết HTML đúng ngay từ đầu thay vì phải chữa cháy sau.

Bài tập thực hành

Bài tập 1 — Phát hiện lệch order. Lấy một trang web bất kỳ bạn thường dùng (ví dụ trang sản phẩm Tiki hoặc Shopee). Nhấn Tab từ đầu trang và ghi lại thứ tự focus di chuyển. So sánh với thứ tự bạn nhìn thấy bằng mắt. Có điểm nào focus "nhảy cóc" không? Ghi lại ít nhất 2 điểm nghi ngờ lệch.

Bài tập 2 — Sửa một layout lệch. Cho đoạn mã sau, hãy viết lại để screen reader đọc theo thứ tự: tiêu đề → mô tả → giá, mà vẫn giữ giá hiển thị ở góc trên bên phải.

<div class="product" style="display:flex; flex-direction:column">
  <span class="price" style="order:-1">1.200.000đ</span>
  <h3>Tai nghe không dây</h3>
  <p>Chống ồn chủ động, pin 30 giờ.</p>
</div>

Gợi ý: sắp lại DOM theo thứ tự logic (h3, p, price) rồi dùng CSS Grid với grid-template-areas để đặt giá lên góc trên phải, thay vì dùng order.

Bài tập 3 — Kiểm thử bằng screen reader. Bật VoiceOver (Mac: Cmd+F5) hoặc NVDA (Windows), mở một trang bạn tự làm, dùng phím mũi tên đọc tuần tự từ đầu đến cuối. Viết ra thứ tự nội dung bạn nghe được và đối chiếu với thứ tự trực quan. Ghi nhận bất kỳ điểm nào gây khó hiểu.

Tóm tắt

Điểm cốt lõi của Bài 41 gói gọn trong một câu: screen reader và bàn phím đi theo DOM order, còn CSS chỉ điều khiển visual order — và khi hai thứ này lệch nhau, người dùng khiếm thị lãnh hậu quả.

Những điều cần nhớ:

  • Screen reader đọc nội dung theo đúng thứ tự trong HTML, từ trên xuống dưới; CSS (order, row-reverse, position, float) có thể sắp xếp lại hình ảnh nhưng không đổi thứ tự đọc.
  • WCAG 1.3.2 Meaningful Sequence (Level A) yêu cầu thứ tự đọc theo chương trình phải phản ánh đúng trình tự logic của nội dung.
  • Nguyên tắc vàng: viết DOM theo thứ tự logic, dùng CSS Grid để định vị trực quan — tránh order và *-reverse cho nội dung có ý nghĩa.
  • Luôn kiểm tra bằng cả hai công cụ: nhấn Tab để soi thứ tự focus, và bật screen reader để nghe thứ tự đọc.
  • Là designer, hãy xác định "thứ tự logic" trước khi thiết kế layout, và bàn giao cho developer một reading order spec rõ ràng.
Khi bạn thiết kế với tư duy "DOM là nguồn chân lý về logic, CSS chỉ làm đẹp", bạn đã tự động loại bỏ được một trong những lớp lỗi accessibility phổ biến và khó phát hiện nhất. Ở bài tiếp theo, chúng ta sẽ bàn về chiến lược và nhịp độ kiểm thử accessibility để những vấn đề như thế này không lọt lưới.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi