Sử dụng luồng đọc CSS để điều hướng tiêu điểm tuần tự hợp lý

Ngày xuất bản: 1 tháng 5 năm 2025

Các thuộc tính CSS reading-flowreading-order có trong Chrome 137. Bài đăng này giải thích lý do thiết kế những thuộc tính này và một số thông tin chi tiết ngắn gọn để giúp bạn bắt đầu sử dụng các thuộc tính đó.

Các phương thức bố cục như lưới và flex đã thay đổi quá trình phát triển giao diện người dùng, tuy nhiên, tính linh hoạt của các phương thức này có thể gây ra vấn đề cho một số người dùng. Rất dễ tạo ra tình huống mà thứ tự hiển thị không khớp với thứ tự nguồn trong cây DOM. Vì thứ tự nguồn này là thứ tự mà trình duyệt tuân theo nếu bạn điều hướng trang web bằng bàn phím, nên một số người dùng có thể gặp phải tình trạng nhảy xung quanh không mong muốn khi họ điều hướng trên một trang.

Thuộc tính reading-flowreading-order đã được thiết kế và thêm vào quy cách Hiển thị CSS để cố gắng giải quyết vấn đề tồn tại lâu dài này.

reading-flow

Thuộc tính reading-flow CSS kiểm soát thứ tự mà các phần tử trong bố cục linh hoạt, lưới hoặc khối được hiển thị cho các công cụ hỗ trợ tiếp cận và cách chúng được lấy tiêu điểm bằng các phương thức điều hướng tuần tự tuyến tính.

Thuộc tính này lấy một giá trị từ khoá, mặc định là normal, giúp duy trì hành vi sắp xếp các phần tử theo thứ tự DOM. Để sử dụng thuộc tính này trong vùng chứa linh hoạt, hãy đặt giá trị của thuộc tính thành flex-visual hoặc flex-flow. Để sử dụng trong vùng chứa lưới, hãy đặt giá trị thành grid-rows, grid-columns hoặc grid-order.

reading-order

Thuộc tính CSS reading-order cho phép bạn ghi đè theo cách thủ công thứ tự của các mục trong một vùng chứa luồng đọc. Để sử dụng thuộc tính này trong một vùng chứa lưới, vùng chứa linh hoạt hoặc vùng chứa khối, hãy đặt giá trị reading-flow trên vùng chứa thành source-order và đặt reading-order của từng mục thành một giá trị số nguyên.

Ví dụ về hộp linh hoạt

Ví dụ: bạn có thể có một vùng chứa bố cục linh hoạt với 3 phần tử theo thứ tự hàng đảo ngược và cũng muốn sử dụng thuộc tính order để sắp xếp lại thứ tự đó.

<div class="box">
 <a href="#">One</a>
 <a href="#">Two</a>
 <a href="#">Three</a>
</div>
.box {
  display: flex;
  flex-direction: row-reverse;
}

.box :nth-child(1) {
  order: 2;
}

Bạn có thể thử di chuyển đến các phần tử này bằng phím TAB để tìm phần tử có thể lấy làm tiêu điểm tiếp theo và phím TAB+SHIFT để tìm phần tử có thể lấy làm tiêu điểm trước đó. Điều này tuân theo thứ tự các mục trong nguồn: Một, Hai, Ba.

Từ góc độ của người dùng cuối, điều này không hợp lý và có thể gây nhầm lẫn. Điều tương tự cũng xảy ra nếu chúng ta sử dụng một công cụ điều hướng không gian hỗ trợ tiếp cận để di chuyển trên trang.

Để khắc phục vấn đề này, hãy đặt thuộc tính reading-flow:

.box {
  reading-flow: flex-visual;
}

Thứ tự tiêu điểm hiện là: Một, Ba, Hai. Đây cũng là thứ tự hiển thị mà bạn sẽ thấy nếu đọc tiếng Anh từ trái sang phải.

Nếu muốn giữ nguyên thứ tự tiêu điểm như dự định ban đầu (theo thứ tự ngược lại), bạn có thể đặt:

.box {
  reading-flow: flex-flow;
}

Thứ tự tiêu điểm hiện là thứ tự linh hoạt đảo ngược: Hai, Ba, Một. Trong cả hai trường hợp, thuộc tính CSS order đều được tính đến.

Ví dụ về bố cục lưới

Để xem cách hoạt động của tính năng này trong một lưới, hãy tưởng tượng rằng bạn đang tạo một bố cục có các mục được đặt tự động trong lưới CSS với 12 vùng có thể lấy tiêu điểm.

<div class="wrapper">
 <a href="#">One</a>
 <a href="#">Two</a>
 <a href="#">Three</a>
 <a href="#">Four</a>
 <a href="#">Five</a>
 <a href="#">Six</a>
 <a href="#">Seven</a>
 <a href="#">Eight</a>
 <a href="#">Nine</a>
 <a href="#">Ten</a>
 <a href="#">Eleven</a>
 <a href="#">Twelve</a>
</div>

Bạn muốn thành phần con thứ năm chiếm không gian lớn nhất ở trên cùng, theo sau là thành phần con thứ hai ở giữa lưới. Tất cả các thành phần con khác đều có thể được tự động đặt trong lưới theo một mẫu cột.

.wrapper {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 100px;
}
.wrapper a:nth-child(2) {
  grid-column: 3;
  grid-row: 2 / 4;
}
.wrapper a:nth-child(5) {
  grid-column: 1 / 3;
  grid-row: 1 / 3;
}

Hãy thử di chuyển qua các phần tử này bằng phím TAB để tìm phần tử có thể lấy làm tiêu điểm tiếp theo và phím TAB+SHIFT để tìm phần tử có thể lấy làm tiêu điểm trước đó. Thứ tự này tuân theo thứ tự của các mục trong nguồn: Từ 1 đến 12.

Để khắc phục vấn đề này, hãy đặt thuộc tính reading-flow:

.wrapper {
  reading-flow: grid-rows;
}

Thứ tự tiêu điểm hiện tại là: Năm, Một, Ba, Hai, Bốn, Sáu, Bảy, Tám, Chín, Mười, Mười một, Mười hai. Nội dung này tuân theo thứ tự hiển thị, từng hàng một.

Nếu muốn luồng đọc tuân theo thứ tự cột, bạn có thể sử dụng giá trị từ khoá grid-columns. Sau đó, thứ tự tiêu điểm sẽ là Năm, Sáu, Chín, Bảy, Mười, Một, Hai, Mười một, Ba, Bốn, Tám, Mười hai.

.wrapper {
  reading-flow: grid-columns;
}

Bạn cũng có thể thử dùng grid-order. Thứ tự tiêu điểm vẫn là từ 1 đến 12. Điều này là do bạn chưa đặt thứ tự CSS cho bất kỳ mặt hàng nào.

Một vùng chứa khối sử dụng reading-order

Thuộc tính reading-order cho phép bạn chỉ định thời điểm một mục cần được truy cập trong quy trình đọc, ghi đè thứ tự do thuộc tính reading-flow đặt. Thuộc tính này chỉ có hiệu lực trên một vùng chứa luồng đọc hợp lệ, khi thuộc tính reading-flow không phải là normal.

.wrapper {
  display: block;
  reading-flow: source-order;
}

.top {
  reading-order: -1;
  inset-inline-start: 50px;
  inset-block-start: 50px;
}

Vùng chứa khối sau đây chứa 5 mục. Không có quy tắc bố cục nào sắp xếp lại các phần tử theo thứ tự nguồn, nhưng có một mục ngoài luồng mà bạn nên truy cập trước.

<div class="wrapper">
  <a href="#">Item 1</a>
  <a href="#">Item 2</a>
  <a href="#">Item 3</a>
  <a href="#">Item 4</a>
  <a class="top" href="#">Item 5</a>
</div>

Bằng cách đặt reading-order của mục này thành -1, thứ tự tiêu điểm sẽ truy cập vào mục này trước khi quay lại thứ tự nguồn cho các mục còn lại trong luồng đọc.

Bạn có thể tìm thêm ví dụ trên trang web chrome.dev.

Tương tác với tabindex

Trước đây, các nhà phát triển đã sử dụng thuộc tính chung tabindex của HTML để giúp các phần tử HTML có thể lấy tiêu điểm và xác định thứ tự tương đối cho chế độ điều hướng tiêu điểm tuần tự. Tuy nhiên, thuộc tính này có nhiều nhược điểm và vấn đề về khả năng tiếp cận. Mối lo ngại chính là điều hướng tiêu điểm được sắp xếp theo tabindex do việc sử dụng tabindex dương tạo ra không được cây hỗ trợ tiếp cận nhận dạng. Khi sử dụng không đúng cách, bạn có thể gặp phải thứ tự tiêu điểm bị gián đoạn, không khớp với trải nghiệm trên trình đọc màn hình. Để khắc phục vấn đề đó, hãy theo dõi thứ tự bằng thuộc tính aria-owns HTML.

Trong ví dụ trước về flex, để có được kết quả tương tự như khi dùng reading-flow: flex-visual, bạn có thể làm như sau.

<div class="box" aria-owns="one three two">
  <a href="#" tabindex="1" id="one">One</a>
  <a href="#" tabindex="3" id="two">Two</a>
  <a href="#" tabindex="2" id="three">Three</a>
</div>

Nhưng điều gì sẽ xảy ra nếu một phần tử khác bên ngoài vùng chứa cũng có tabindex=1? Sau đó, tất cả các phần tử có tabindex=1 sẽ được truy cập cùng nhau, trước khi chúng ta chuyển sang giá trị tabindex gia tăng tiếp theo. Thao tác điều hướng tuần tự bị gián đoạn này sẽ khiến người dùng có trải nghiệm không tốt. Do đó, các chuyên gia về khả năng tiếp cận khuyên bạn nên tránh sử dụng tabindex dương. Chúng tôi đã cố gắng khắc phục vấn đề này khi thiết kế reading-flow.

Vùng chứa có thuộc tính reading-flow được đặt sẽ trở thành chủ sở hữu phạm vi lấy tiêu điểm. Điều này có nghĩa là nó sẽ giới hạn phạm vi điều hướng tiêu điểm tuần tự để truy cập vào mọi phần tử bên trong vùng chứa trước khi chuyển sang phần tử có thể lấy tiêu điểm tiếp theo trong tài liệu web. Ngoài ra, các phần tử con trực tiếp của thành phần này được sắp xếp bằng cách sử dụng thuộc tính reading-flow và tabindex dương sẽ bị bỏ qua cho mục đích sắp xếp. Bạn vẫn có thể đặt chỉ mục thẻ dương cho các phần tử con của một mục trong luồng đọc.

Xin lưu ý rằng một phần tử có display: contents kế thừa thuộc tính reading-flow từ phần tử gốc bố cục cũng sẽ là một vùng chứa luồng đọc hợp lệ. Hãy lưu ý điều này khi thiết kế trang web của bạn. Đọc thêm về vấn đề này trong yêu cầu phản hồi của chúng tôi về reading-flowdisplay: contents.

Hãy cho chúng tôi biết

Hãy thử các ví dụ trong bài đăng này và trong reading-flowcác ví dụ trên chrome.dev, đồng thời sử dụng các thuộc tính CSS này trên trang web của bạn. Nếu bạn có ý kiến phản hồi, hãy nêu ý kiến đó dưới dạng một vấn đề trên kho lưu trữ GitHub của Nhóm công tác CSS. Nếu bạn có ý kiến phản hồi cụ thể về chỉ mục thẻ và hành vi đặt phạm vi tiêu điểm, hãy nêu ý kiến đó dưới dạng một vấn đề với kho lưu trữ HTML WHATNOT GitHub. Chúng tôi rất mong nhận được ý kiến phản hồi của bạn về tính năng này.