使用 CSS 阅读流实现逻辑顺序的焦点导航

发布时间:2025 年 5 月 1 日

CSS reading-flowreading-order 属性自 Chrome 137 起可用。本文介绍了这些属性的设计原因,并提供了一些简要的详细信息,帮助您开始使用这些属性。

网格和 Flex 等布局方法改变了前端开发,但它们的灵活性可能会给某些用户带来问题。很容易出现视觉顺序与 DOM 树中的源顺序不匹配的情况。由于浏览器在您使用键盘浏览网站时会遵循此来源顺序,因此部分用户在浏览网页时可能会遇到意外的跳动。

reading-flowreading-order 属性已设计并添加到 CSS 显示规范中,以尝试解决这个长期存在的问题。

reading-flow

reading-flow CSS 属性用于控制 flex、网格或块布局中的元素向无障碍工具公开的顺序,以及使用线性顺序导航方法聚焦这些元素的方式。

它接受一个关键字值,默认值为 normal,该值可保持按 DOM 顺序对元素进行排序的行为。如需在 flex 容器中使用它,请将其值设置为 flex-visualflex-flow。如需在网格容器内使用它,请将其值设置为 grid-rowsgrid-columnsgrid-order

reading-order

借助 reading-order CSS 属性,您可以手动替换阅读流容器中各项的顺序。如需在网格、弹性或块容器中使用此属性,请将容器的 reading-flow 值设置为 source-order,并将各个项目的 reading-order 设置为整数值。

Flexbox 中的示例

例如,您可能有一个 flex 布局容器,其中包含三个按反向行顺序排列的元素,并且还想使用 order 属性来重新调整该顺序。

<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;
}

您可以尝试使用 TAB 键导航这些元素,以查找下一个可聚焦元素;使用 TAB+SHIFT 键查找上一个可聚焦元素。 这会按源顺序显示项:One、Two、Three。

从最终用户的角度来看,这毫无意义,而且可能会非常令人困惑。如果我们使用无障碍空间导航工具在网页上导航,也会发生同样的情况。

如要解决此问题,请设置 reading-flow 属性:

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

焦点顺序现在为:一、三、二。这与从左到右阅读英语时获得的视觉顺序相同。

如果您希望保持原来的焦点顺序(即反向顺序),可以设置:

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

焦点顺序现在与 flex 顺序相反:Two、Three、One。在这两种情况下,系统都会考虑 CSS order 属性。

网格布局示例

为了直观地了解此功能,不妨想象一下,您正在使用 CSS 网格自动放置项创建一个布局,其中包含 12 个可聚焦区域。

<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>

您希望第五个子项占据最顶部的最大空间,然后是第二个子项占据网格中间的空间。所有其他子项都可以按照列模板自动放置在网格中。

.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;
}

尝试使用 TAB 键导航到这些元素,以查找下一个可聚焦元素;使用 TAB+SHIFT 键查找上一个可聚焦元素。这会按来源顺序(从 1 到 12)显示商品。

如要解决此问题,请设置 reading-flow 属性:

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

焦点顺序现在为:5、1、3、2、4、6、7、8、9、10、11、12。它会按视觉顺序逐行进行。

如果您希望阅读顺序改为按列顺序进行,则可以使用 grid-columns 关键字值。焦点顺序随后会变为“五”“六”“九”“七”“十”“一”“二”“十一”“三”“四”“八”“十二”。

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

您也可以尝试使用 grid-order。焦点顺序保持为“一”到“十二”。 这是因为没有任何商品设置了 CSS 订单。

使用 reading-order 的块容器

借助 reading-order 属性,您可以指定在阅读流程中应何时访问某个项目,从而替换 reading-flow 属性设置的顺序。仅当 reading-flow 属性不为 normal 时,此属性才会对有效的阅读流程容器生效。

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

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

以下块容器包含 5 个项目。没有可重新排序元素的布局规则,但有一个脱离正常流程的项应先访问。

<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>

通过将此项的 reading-order 设置为 -1,焦点顺序会先访问此项,然后再回退到源顺序以访问其余的阅读流项。

您可以在 chrome.dev 网站上找到更多示例。

与 tabindex 的互动

过去,开发者一直使用 HTML tabindex 全局属性来使 HTML 元素可聚焦,并确定顺序聚焦导航的相对顺序。不过,此属性存在许多缺点,并且存在无障碍功能方面的问题。主要问题在于,使用正 tabindex 创建的 tabindex 排序焦点导航无法被可访问性树识别。如果使用不当,可能会导致焦点顺序跳跃,与屏幕阅读器的体验不符。若要解决此问题,请使用 aria-owns HTML 属性跟踪排序。

在之前的 Flex 示例中,若要获得与使用 reading-flow: flex-visual 相同的结果,您可以执行以下操作。

<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>

但是,如果容器之外的其他元素也具有 tabindex=1,会发生什么情况? 然后,系统会一起访问所有 tabindex=1 元素,然后再移至下一个递增的 tabindex 值。这种跳跃式的顺序导航会导致糟糕的用户体验。因此,无障碍专家建议避免使用正 tabindex。我们在设计 reading-flow 时尝试修复了此问题。

设置了 reading-flow 属性的容器会成为焦点范围所有者。 这意味着,它将顺序焦点导航的范围限定为先访问容器内的每个元素,然后再移动到网页文档中的下一个可聚焦元素。此外,其直接子元素会使用阅读顺序属性进行排序,并且为了排序目的会忽略正 tabindex。仍然可以为阅读流项的后代设置正 tabindex。

请注意,如果某个具有 display: contents 的元素从其布局父项继承了 reading-flow 属性,则该元素也将成为有效的阅读流容器。在设计网站时,请注意这一点。如需详细了解相关信息,请参阅我们针对 reading-flowdisplay: contents 征求反馈的博文

请告诉我们

不妨试试本文和 chrome.dev 上的 reading-flow 示例,并在您的网站上使用这些 CSS 属性。如果您有任何反馈,请在 CSS 工作组 GitHub 代码库中将其作为问题提出。如果您对 tabindex 和焦点范围界定行为有具体反馈,请在 HTML WHATNOT GitHub 代码库中将其作为问题提出。我们非常希望您能就此功能提供反馈。