我们如何使用 Gravity UI 设计系统使 Yandex Cloud 更易访问

你好,我叫沃瓦·季莫费耶夫,是 Yandex Cloud 的技术项目经理。本文将分享我们如何让云平台网站更具无障碍性、经历了多少轮迭代,以及 Gravity UI 在其中扮演了什么角色。

所有服务无障碍性的基础,在于它们对屏幕阅读器(Screen reader,屏幕辅助访问程序)的支持程度。通过这些程序,存在限制的用户可以感知界面并与之交互。

网站也不例外。我们需要弄清楚,Yandex Cloud 对所有用户而言到底有多“无障碍”。

在 Yandex,我们所说的无障碍是指:无论是暂时性的还是永久性的身体限制,所有人都应当能够舒适地使用我们的服务。例如,目前 Yandex 已有 16 个服务为盲人用户做了适配:Lavka、Go、搜索、浏览器、邮箱等。在每个服务的无障碍工作中,非视觉测试团队都会提供帮助——我将在本文讲述的案例同样离不开他们的支持。

剧透:测试结果发现了若干在使用屏幕阅读器时存在争议/不理想的点,并最终转化为具体的工作任务。

不过,我们先从头说起。

一切从 Gravity UI 开始

Gravity UI 是一个设计系统和组件库,Yandex Cloud 网站以及云端数十个其他产品都基于它运行。它已 开源 并对所有人可用(我们也很高兴看到过去半年 community 聊天群的活跃度明显提升)。

我们提供了:

  • 一套基础 React 组件;
  • 用于落地页的构建器库;
  • 由设计师编写的组件使用详细指南
  • Figma 资源库;
  • 近 600 个现成图标的集合;
  • ChartKit —— 数据可视化包;
  • Yagr —— 基于 uPlot 的高性能图表渲染;
  • i18n —— 界面本地化包;
  • 其他实用库

2024 年 3 月,核心库迎来更新——UIKit 第 6 版。该版本更新了 List 组件,为所有组件加入了 RTL 参数支持,并带来一组提升无障碍性的 a11y 改进。

UIKit 第 6 版有哪些新内容
  1. List 2.0 组件。UIKit 最初就有 List,但我们希望对其进行一些改进。汇总需求时,我们整理出以下清单:

    • 支持不同尺寸与宽度;
    • 列表项支持图标,以及不同数量/不同位置的图标;
    • 支持状态(states);
    • 列表项支持不同内容(单行、多行或用户列表);
    • 支持不同分隔线与分组方式。

    这些改动较大,因此我们创建了 List 2.0。目前它以 prestable 版本发布,但我们建议用户迁移并提供反馈。

  2. RTL。如果你的应用或网站需要以希伯来语、阿拉伯语等从右到左书写的语言显示,就需要支持 RTL 标准。同时在 RTL 场景下:

    • 插入的拉丁字母单词按从左到右书写;
    • 数字按从左到右书写;
    • 阿拉伯语标点同样按从左到右书写,等等。

    我们在所有组件中支持了 RTL 参数。为了提供完整示例,我们制作了一个阿拉伯语展示页。实现方式可在 landing 的源码中查看。也可以在 storybook 中查看示例。

  3. 无障碍(a11y)

    • 在项目中加入了 eslint 插件;
    • 为 Persona 组件的 clickable 和 closable 状态加入键盘支持;
    • 禁用了 15 个非交互组件的 onClick;
    • 在 SelectionTable 组件中加入键盘支持。

我们如何推进 a11y 改进

这离不开非视觉测试团队及其负责人阿纳托利·波普科的帮助。在一次会议中,阿纳托利一步步测试了沙盒以及 Gravity UI 的网站,以了解当前有哪些无障碍问题。

我们通过键盘和屏幕阅读器的特殊指令在网站上移动,以检查组件的无障碍性。

从视觉上看,大致是这样:

Full screen image

会后,团队拿到了具体的工作任务;在 GitHub 上也新增了 3 个 与基础组件相关的 issue。

详情如下:

  • 在下拉列表中,第二层级的条目无法通过键盘
    展开
    只能通过鼠标点击。
Full screen image
  • 在 Select 组件里,难以 判断 markdown 的项目符号列表当前选中了哪一项。

  • 仅用图形标识、没有文本标签的 按钮 会被读成仅仅“按钮”或“单选按钮”。该 bug 只出现在落地页上; 组件本身支持 aria-label,但我们没有使用。

Full screen image

最终我们意识到:既然没有找到几十条问题,说明该库的无障碍性 已经处于不错的水平。脱离真实上下文来测试组件非常困难, 因此我们决定开始对成品进行无障碍检查。 这样我们也找到了更多可改进的 a11y 点。

Yandex Cloud 的无障碍性

受到 Gravity UI 更新的启发,我们决定对自家服务进行无障碍测试:先从 Yandex Cloud 网站开始,之后把经验推广到其他界面。

实现无障碍有一系列标准可遵循。但为了更可靠地测试 Yandex Cloud 的界面,并更好地理解我们的网站对所有人是否友好,我们进行了审计(audit)。

无障碍审计

我们再次联系了非视觉测试团队与阿纳托利,共同测试网站、记录问题并带回进行修复。总共进行了两轮,间隔将近一个月——测试与复测。

在测试过程中,我们记录了一整批需要落实的改动。

首页

  • 搜索控件需要改造。在我们的界面里,搜索组件以带放大镜图标的搜索区域形式呈现,点击后才会展开输入框。此前的实现对屏幕阅读器而言是相互独立的元素,导致盲人用户困惑。

    image

  • 语言选择使用了“折叠”状态属性,但本质上它是选择按钮而非下拉列表。缺少“语言”标签,“语言 — 地区”的切换文本中也缺少空格。

  • 账户菜单没有进行焦点锁定:激活后用户会跳出菜单项范围。

  • 首页的 main 标签重复了,因此需要移除一个。

  • 示例区:需要用标签页(tab)替代按钮。

Full screen image
  • 在示例卡片中,文本被放到了 aria-describedby 里, 这导致屏幕阅读器只会朗读一次;在学习重要信息时并不方便。 在排查这个 bug 时,我们意识到需要对 Card 组件进行一次系统性的 重构,于是创建了一个 issue,你可以在其中了解变更细节并参与讨论。

导航

  • 焦点问题:展开菜单的一级项时,需要将焦点移动到子菜单。
  • 需要移除菜单一级项上的 TabIndex。当前实现会导致所有菜单项被重复朗读两次。
  • 当导航展开时,需要将键盘焦点锁定在导航内;否则可能从菜单“跳出”到页面内容,之后无法回到菜单。
  • Yandex Cloud 被包在一个列表中。页脚的链接列表标题被包在 li 元素里,因此 NVDA 会把它们识别为列表的一部分,并把同一个列表向用户朗读两遍。
  • AppStore、Google Play 的链接缺少标签。测试时朗读的是 URL 片段,用户无法理解。

「博客」版块

  • 「所有主题」「所有服务」按钮没有正确关联为按钮。Select 的按钮不会朗读其内容。
  • 列表需要无障碍支持:打开下拉列表时,屏幕阅读器焦点应切换到列表项;此外,应支持用普通方向键在列表项间简化移动——无需任何组合键。

博客文章

  • 面包屑中缺少「您在这里」元素。
  • 需要将焦点锁定在对话框内。
  • 收藏计数器缺少名称。计数器是一个带图标与数字的按钮;屏幕阅读器只朗读数字——没有图标就无法理解按钮用途。

我们把所有任务汇总到一个 epic 并开始推进。其中部分任务在 GitHub 上创建了 issue。

下面我会更详细地讲几个最有意思的案例。

Select 组件

Full screen image

测试时我们发现:使用键盘导航时,列表项名称不会被屏幕阅读器朗读。 我们通过 aria-activedescendant 属性部分修复了问题,但并未完全解决。

仍然存在的问题

  • 在 Safari 中该方案无效,目前还不清楚如何解决。

  • 搜索过滤并非总是受支持(实际上是从未支持——因为未实现)。通常 aria-activedescendant 会挂在下拉列表的主元素上,并指向列表中被选中的元素。现在它挂在打开下拉列表的按钮上。按上/下箭头时,我们把按钮的 aria-activedescendant 值改为列表中的上一个/下一个元素。这样屏幕阅读器就能从按钮上读到当前选中了哪一项。

    搜索过滤的问题在于:它的输入框没有 aria-activedescendant 属性。如果用户将焦点放在过滤输入框中并想输入内容,屏幕阅读器无法从该输入框读取当前哪个列表项是激活的,因此用方向键在列表中导航会失效。

  • 选中的选项没有标记,需要为其添加属性,例如 aria-selected。

当前问题的 issue 见 这里

Full screen image

面包屑是一种导航元素,用于展示用户在网站中的路径。 在我们的场景里,屏幕阅读器会把整条路径全部读出来, 让人无法理解自己处于网站的哪个部分。 此外,整个组件只被呈现为一组链接。

我们决定稍微偏离标准,用更简单且更优的方式解决: 为屏幕阅读器加上「您在这里」标签。最终我们把标准做法与该标签结合起来。

在实现过程中我们发现:要让该标签被朗读,需要把它挂到 不会被屏幕阅读器忽略的元素上。 与其想办法绕开既有结构,不如把标签挂到约定的结构上更简单。 尽管如此,这个标签仍然有价值:它能帮助用户更快理解自己听到的是面包屑导航。

没有说明的图片

网站上一些图片没有配说明,屏幕阅读器会把它们朗读为「图片」。 这对用户没有任何信息价值,也无法帮助理解界面, 因此我们决定对屏幕阅读器隐藏没有说明的图片。

区块内文本顺序

Full screen image

在我们网站的多个位置,信息以单一块的形式呈现, 例如活动卡片或博客文章卡片。

我们注意到屏幕阅读器朗读信息的顺序,与明眼用户阅读顺序并不一致。 对于活动卡片,屏幕阅读器朗读顺序是:报名状态、时间、地点, 最后才是标题和活动描述。

通常用户不会线性地阅读:先看标题,再看副标题和图片。 问题在于屏幕阅读器会按页面 DOM 树中的顺序读取元素。 为了修正顺序,我们不得不重组 DOM 结构。

Full screen image

我们很惊讶,但在某些「操作系统—浏览器」组合中这仍然无效。 例如在 macOS 的 Mozilla Firefox 上,尽管我们改变了 DOM 树顺序, 问题依然存在。 希望 Firefox 开发者能在新版本中修复这种行为。

模态窗口

Full screen image

当明眼用户在界面中打开弹窗时,视线会落在弹窗内容上。 但整个网站的其他内容仍然可见,必要时也能把注意力移回去。

当通过屏幕阅读器使用网站时,情况会不同。 如果弹窗不是模态的,它就没有边界。 导航会变得更困难:用户可能在使用元素导航时误操作从弹窗中“跳出去”。

模态窗口有相关标准;按标准做,并不一定要把界面中的所有弹窗都锁定起来。

但在测试过程中我们得出结论:把弹窗做成模态,是一种良好实践, 能让屏幕阅读器用户更容易使用网站。

因此我们决定把所有弹窗都做成模态。 同时,我们也保留了让用户配置替代交互方案的能力。

你可以通过设置 role="dialog", aria-modal="true" 来启用该方案。 在 VoiceOver + Firefox 的组合中该方案不受支持:详情见已关闭的 issue

Diplodoc

受到我们这股劲头的带动,用于创建技术文档的平台 Diplodoc 的开发者也在其产品中做了一系列 a11y 改进:

  • 检查了 YFM(Yandex Flavored Markdown)的所有扩展语法元素,并重写使其可被屏幕阅读器识别。
  • 优化了通过键盘使用文档的流程:修正元素选择顺序,并让所有交互元素都支持选择操作。
  • 为界面元素添加了正确的标签与标识,显著简化屏幕阅读器用户对服务的使用。

由于我们使用 Diplodoc 来展示自己的文档,这些改进也提升了 Yandex Cloud 网站上文档的无障碍性。

结果与计划

我们已迈出第一步,开始为所有用户提升 Yandex Cloud 各界面的无障碍性。接下来,我们需要把经验迁移到服务的其他界面,并继续修复目前已知的问题。

Gravity UI 让我们的无障碍工作更轻松;我们也在持续处理带有 a11y 标签的未解决 issue。本文撰写时,我们有 14 个 open issue 和 24 个 closed issue,你可以在这里查看。

Full screen image

欢迎提交 PR 并留言讨论无障碍与服务使用体验,也欢迎分享你在网站中使用 Gravity UI 的案例。

image

弗拉基米尔·季莫费耶夫
Yandex Cloud 技术项目经理

我们如何使用 Gravity UI 设计系统使 Yandex Cloud 更易访问

Sign in to save this post