Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
301 changes: 301 additions & 0 deletions _articles/zh-hans/accessibility-best-practices-for-your-project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
---
lang: zh-hans
title: 项目无障碍最佳实践
description: 让你的开源项目对所有人(尤其是残障人士)可用的实用、可落地的步骤。
class: accessibility-best-practices
order: -1
image: /assets/images/cards/accessibility-best-practices.png
---

无障碍(accessibility,常简写为 _a11y_)意味着不论用户是否残障、使用何种辅助技术、身处何种环境或使用何种设备,都能使用你的项目。它包括但不限于:对屏幕阅读器的支持、纯键盘导航、字幕/文字记录、足够的色彩对比度,以及清晰的内容结构。

## 与残障人士携手合作

**"没有我们的参与,就不要替我们做决定"** —— 对无障碍建设而言,最重要的一件事就是把它所服务的人群放在中心位置。有残障经历的用户、贡献者和测试者,能以指南和自动化工具无法企及的方式理解真正的障碍所在。尽早并持续地寻求他们的真实体验。

### 落到实处

脱离受影响的人群做出的决定,往往会偏离目标。与残障人士一起构建,而不是替他们构建,才能打造出对所有人都更好的软件。

以下是几种纳入真实体验的方式:

* 邀请残障贡献者参与设计讨论,而不仅仅是缺陷分类(bug triage)。
* 在条件允许的情况下,邀请残障人士参与可用性测试和反馈。
* 当有人描述他们如何使用你的项目时,认真倾听,即使这挑战了你原有的假设。
* 把无障碍报告当作专业意见来对待,而不是抱怨——它们所代表的用户可能比你想象的更多。

### 无障碍让所有人受益

* **它影响着大量人群。** 根据[世界卫生组织](https://www.who.int/news-room/fact-sheets/detail/disability-and-health)的估计,全球约有 13 亿人(六分之一)存在显著的残障情况。
* **它是质量的一部分。** 具备无障碍能力的产品,往往对所有人都更易用。
* **它降低支持负担。** 更清晰的界面和文档意味着更少困惑的用户。
* **它扩大你的贡献者群体。** 辅助技术用户能够更充分地参与进来。
* **它推动创新。** 为多样化需求而设计,往往会带来让所有人都受益的功能(比如字幕、语音控制和深色模式,最初都是无障碍方案)。
* **它常常是硬性要求。** 许多组织(以及一些政府)在采购和合规方面都要求具备无障碍能力。
* **我们的未来充满不确定性。** 没有人能确定自己明天还拥有今天所拥有的能力。

## 从无障碍声明开始

在动手写代码之前,先花点时间记录下你的项目对无障碍的承诺。一份无障碍声明向用户和贡献者传达了一个信号:无障碍是优先事项,而不是事后补丁。具体做法可参考 [W3C 的《撰写无障碍声明》指南](https://www.w3.org/WAI/planning/statements/)。

添加一份清晰的声明,设定预期,并让用户能方便地报告问题。你可以直接在 README 中添加一个无障碍章节,也可以创建独立的 **ACCESSIBILITY.md** 文件,并在 README 中链接到它以提高可见性。可参考这个 [ACCESSIBILITY.md 示例](https://github.com/open-source-accessibility/accessibility-toolkit/blob/main/ACCESSIBILITY.md)。

### 目标

* 陈述可衡量的目标和准则(在可行的情况下,参考 [WCAG AA](https://www.w3.org/TR/WCAG22/#wcag-2-layers-of-guidance))。
* 明确首要优先事项,以及你打算如何实现它们(键盘与屏幕阅读器支持、字幕与文字记录等)。
* 说明已知的局限性,以及可用的替代方案(如果存在)。

### 贡献者要求

设立清晰的准则,让贡献者知道项目对他们的期望:

* **测试:** 所有 UI 改动都必须使用无障碍测试工具进行测试(例如 [Axe DevTools](https://www.deque.com/axe/devtools/extension/#:~:text=Try%20Axe%20DevTools%20Extension%20in%20your%20browser%20of%20choice))。
* **文档:** 针对 SVG、图片、交互元素等组件,遵循项目的无障碍指南。
* **CI/CD:** 如果 PR 引入了无障碍检查工作流检测到的违规项,应当让检查失败。

### 支持的环境

* 列出你所支持的平台(Web、移动端 Web、iOS、Android、终端/CLI、桌面应用)。
* 列出任何部分支持的说明。

### 报告无障碍问题

* 引导报告者使用无障碍问题模板来创建 issue。
* **小贴士:** 诚实地设定预期(比如"我们正在处理这个问题——进展跟踪见 ISSUE-123");确认收到报告,并在可能的情况下提供后续进展或临时解决方案。

#### 为什么要把无障碍问题从常规问题流程中独立出来?

用户早已习惯了一份专门的无障碍声明和报告路径——这在私营部门和各类政府网站中都是行之有效的惯例,很多用户在遇到障碍时会首先寻找它。让无障碍问题独立于常规缺陷流程,原因在于:

* **影响具有时效性。** 无障碍缺陷可能导致用户完全无法使用你的项目,而不只是带来不便。独立的报告路径有助于这类问题被更快地分类处理。
* **上下文不同。** 无障碍问题报告需要具体信息(所用辅助技术、操作系统、浏览器、严重程度),而通用的缺陷模板不会主动提示这些内容。
* **它传达出承诺。** 一份可见的、独立的声明向用户和贡献者表明,无障碍是一等公民关切,而不是被塞进"其他缺陷"里草草处理。
* **报告者本身可能正在使用辅助技术来提交报告。** 一个清晰、可预期的流程(固定的文件、固定的标签、固定的模板)能减少受影响最严重的那部分人所面临的阻力。

## 让文档默认无障碍

文档往往是用户接触到的第一个"界面"。确保每个人都能读懂它。

### 结构与语义

* 使用**合乎逻辑的标题层级**,不要跳级(`#`、`##`、`###`、`####`、`#####`、`######`)。
* 使用**独特且具描述性的链接文字**(用"阅读贡献指南"而不是"点击这里")。
* 使用平实的语言,避免行话,首次出现的缩写要展开说明。
* [使用**真正的列表**](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#lists),而不是手动打出的编号。
* 让**帮助和导航保持在各页面一致的位置**,方便用户可预期地找到它们。
* 避免仅通过位置或样式来传达含义(例如"看右边的红色文字")。

### 图片、图表与视频

* 为图片提供有意义的**替代文本**(常简称为"alt text",参考 [W3C 的 alt 决策树](https://www.w3.org/WAI/tutorials/images/decision-tree/))。
* 尽量使用真实文本,而不是文字图片。
* 对于复杂图片(如架构图),在附近提供额外的**文字说明**(要点列表或简短解释)。
* 如果你发布演示、教程、演讲或发布视频:
* 提供**字幕**(尽量选择人工校对过的版本)。
* 提供**文字记录**。
* 避免自动播放音视频。
* 用语言描述重要的屏幕操作。

### 表格

* 表格只用于呈现表格数据,不要用于页面布局。
* 提供**表头单元格**,将列标题和行标题与数据单元格关联起来。
* 提供**说明或摘要**,描述表格的用途。

### 代码块

* 保持每行长度合理(自动换行有助于可读性)。
* 不要仅依赖颜色高亮来传达含义。
* 用文字说明代码做了什么、成功的标志是什么。

## 设计无障碍的界面

如果你的项目有 Web 界面,以下这些高影响力的默认设计能帮助到所有用户。

### 键盘支持

* 所有可交互元素都应能**仅通过键盘**访问和操作。
* 确保有**可见的焦点指示**(除非提供替代方案,否则不要移除焦点轮廓)。
* 保持与视觉布局一致的、合乎逻辑的 **Tab 键顺序**。
* 除非你有意管理焦点(例如模态对话框)并提供退出方式,否则不要在组件内困住焦点。

### 语义优先

* 尽可能使用**原生 HTML** 元素(`<h1>`、`<button>`、`<a>`、`<input>`、`<label>`)。
* 只有在原生 HTML 不够用时才使用 **ARIA**。没有 ARIA 也好过糟糕的 ARIA。如果确实需要使用,请遵循 [ARIA(无障碍富互联网应用)文档](https://www.w3.org/TR/wai-aria/),并确保所有可交互的 ARIA 控件都支持键盘操作。
* 声明文档的**语言**(例如 HTML 中的 `lang="en"`),并标注其中语言不同的部分。

### 名称、标签、说明

* 每个表单控件都需要关联一个**标签**。
* 提供**清晰的错误信息**,指出哪个字段出错,并通过程序化方式(如 `aria-describedby`)将错误信息与字段关联。
* 对于必填字段,用文字说明要求(而不只是一个星号)。

### 颜色与对比度

* 不要仅用颜色来传达含义(例如"错误是红色的")。
* 确保文字、图标和 UI 控件有足够的对比度(参考 [WebAIM 的对比度检测工具](https://webaim.org/resources/contrastchecker/))。

### 动效与动画

* 避免闪烁内容和快速动画。
* 避免视差效果和自动轮播,或者让它们可以被关闭和控制。
* 如果操作系统表明用户要求减少或关闭动效,就避免不必要的动画。

### 动态内容

当内容在不刷新页面的情况下发生更新时,要确保辅助技术用户能够获知:

* 谨慎地使用合适的 **ARIA live region** 来发出通知。
* 在打开/关闭对话框、菜单和抽屉时妥善管理焦点。

### 依赖与模式

* 使用有完善无障碍支持文档的组件库。
* 追踪上游的无障碍缺陷,并在你的 issue 中关联它们。
* 对自定义 UI 控件保持谨慎。原生控件(如 `<button>`、`<select>`、`<input type="checkbox">`、`<details>`)自带浏览器和辅助技术已经理解的键盘支持、焦点管理、屏幕阅读器语义和表单集成能力。在自定义组件中重新实现这些行为既耗时又容易出错,并且会随着平台和辅助技术的演进带来长期维护成本。只有在原生元素确实无法满足需求时,才考虑使用自定义控件。

### 移动端注意事项

* 让触控目标至少达到 **24×24 CSS 像素**。
* 为多指或路径手势(如捏合、滑动)提供单点替代方案。
* 为拖放操作提供替代方案(按钮、菜单)。
* 除非内容本身确实需要特定方向,否则不要将内容限制在单一显示方向上。
* 为由设备运动触发的功能(如摇一摇撤销)提供替代方案。

## 让工具无障碍

只要设计得当,命令行工具和仪表盘也可以做到高度无障碍。

### CLI 工具

命令行应用只要具备可预期性和可脚本化,就能做到高度无障碍。

* 支持 `--help`,并提供清晰的用法示例。
* 为难以解析表格的用户提供**机器可读的输出**选项(如 `--json`)。
* 不要仅依赖 ANSI 颜色来传达成功/失败,要同时提供文字标签和退出码。
* 编写错误信息时,应当:
* 说明发生了什么,
* 展示如何修复,以及
* 在需要时链接到文档。
* 使用标准的退出码,并确保失败时返回非零值。

### 终端、日志与仪表盘

* 优先使用平实语言,而非行话。
* 避免使用未加说明的缩写。
* 对严重程度级别(`ERROR`、`WARN`、`INFO`)使用一致的格式,并在有用时包含时间戳。
* 确保"状态"不是仅通过颜色来传达的。

## 把无障碍融入贡献流程

当无障碍成为常规流程的一部分时,它会更容易维持下去。

### 添加 issue 标签和模板

* 创建一个无障碍标签(例如 _"accessibility"_ 或 _"a11y"_)。
* 创建一个无障碍 issue 模板,包含:
* _accessibility_ 标签
* 预期行为与实际行为
* 复现步骤(可选附带屏幕录制)
* 所用工具(操作系统、浏览器、辅助技术及其版本)
* 用于优先级排序的严重程度分类:
* **严重(Critical):** 阻止用户完成核心任务(例如"无法结算")。
* **高(High):** 存在明显困难,但有变通方案。
* **中(Medium):** 造成困扰或体验不一致。
* **低(Low):** 对可用性影响很小的小问题。
* 如有需要,附上联系方式或升级处理的说明。

可参考这个[无障碍 issue 模板示例](https://github.com/open-source-accessibility/accessibility-toolkit/blob/main/.github/ISSUE_TEMPLATE/accessibility.yml)。

### 在 Pull Request(PR)中添加无障碍检查清单

对于涉及 UI 改动的项目,可以包含如下问题:

* 键盘导航能否端到端正常工作
* 焦点状态是否可见且符合逻辑
* 表单是否有标签,错误是否会被朗读出来
* 颜色是否不是传达含义的唯一方式
* 是否遵循了"减少动效"的系统设置(如果新增了动画)
* 是否至少检查过一次屏幕阅读器下的行为

可参考这个 [PR 模板示例](https://github.com/open-source-accessibility/accessibility-toolkit/blob/main/.github/PULL_REQUEST_TEMPLATE.md)。

### 明确"完成"的定义

为功能和缺陷修复添加无障碍验收标准,让它不再是可选项或临时补救。

### 善用 GitHub Copilot

* 创建专门的 Copilot 智能体,将无障碍相关任务自动化融入开发流程,从使用 [axe-core](https://github.com/dequelabs/axe-core) 审计页面,到跨版本追踪无障碍改进情况。参考[《GitHub Copilot 自定义智能体无障碍入门指南》](https://accessibility.github.com/documentation/guide/getting-started-with-agents/)。
* 根据你的编码风格、无障碍实践和项目背景,定制 Copilot 的建议,确保它们符合你的无障碍要求。参考[《使用自定义指令优化 GitHub Copilot 的无障碍表现》指南](https://accessibility.github.com/documentation/guide/copilot-instructions/)。

### 得体而有效地处理无障碍问题报告

无障碍问题往往难以描述、难以复现,并且对报告者能否使用你的项目具有时效性。处理这类报告时:

* 感谢报告者,并不带质疑地提出澄清性问题。
* 优先处理阻断性问题(无法完成核心流程),而不是外观类问题。
* 在可能的情况下提供变通方案。
* 闭环处理:如果报告者愿意,与他们确认修复是否有效。

## 持续测试无障碍性

自动化工具擅长捕捉回归问题,但只有人工测试才能建立起真正的信心。

### 自动化检查(擅长捕捉回归)

* 在 UI 代码中进行无障碍相关的 lint 检查。
* 在 CI 中自动扫描常见的 WCAG 违规项(例如使用 [GitHub Accessibility Scanner](https://github.com/github/accessibility-scanner))。
* 编写单元/集成测试,对关键组件断言其 [role/name](https://www.w3.org/TR/accname-1.2/)。

### 人工测试(建立真正信心所必需)

* **纯键盘**测试:不用鼠标,能否顺利完成主要流程?
* **屏幕阅读器**抽查:
* macOS:[VoiceOver](https://support.apple.com/guide/voiceover/welcome/mac)
* Windows:[NVDA](https://www.nvaccess.org/about-nvda/)(在开源社区中常用)、[JAWS](https://vispero.com/jaws-screen-reader-software/)(企业场景常用)
* **缩放与重排**:在 200% 缩放和窄屏宽度下测试。
* 在适用的情况下测试**高对比度/强制颜色**模式。

**小贴士:** 在发布检查清单中加入一个轻量的"无障碍[冒烟测试](https://en.wikipedia.org/wiki/Smoke_testing_(software))"环节。

## 本周就能开始的一些小改进

### 你不需要一次做完所有事,可以先从几个能快速见效的改进入手。

挑几项来做:

* 添加 `ACCESSIBILITY.md` 文件,并创建一个无障碍标签(如 _"accessibility"_ 或 _"a11y"_)
* 确保每个可交互元素都能通过键盘访问
* 修复缺失的表单标签
* 声明文档的**语言**(例如 HTML 中的 `lang="en"`),并标注其中语言不同的部分
* 为 README 和文档添加替代文本和标题结构
* 在 PR 检查清单中加入键盘/焦点相关条目
* 为你最受欢迎的视频添加字幕/文字记录
* 为某个 CLI 命令添加 `--json` 输出

### 有助于将无障碍承诺正式落地的建议文件

可以考虑在你的仓库中添加以下文件:

* `ACCESSIBILITY.md`:你的无障碍声明、问题报告方式,以及任何项目特定的指导(组件规则、模式、已知问题)——[ACCESSIBILITY.md 示例](https://github.com/open-source-accessibility/accessibility-toolkit/blob/main/ACCESSIBILITY.md)
* `.github/ISSUE_TEMPLATE/accessibility.yml`:无障碍缺陷报告模板——[无障碍 issue 模板示例](https://github.com/open-source-accessibility/accessibility-toolkit/blob/main/.github/ISSUE_TEMPLATE/accessibility.yml)
* `.github/pull_request_template.md`:包含无障碍检查清单——[PR 模板示例](https://github.com/open-source-accessibility/accessibility-toolkit/blob/main/.github/PULL_REQUEST_TEMPLATE.md)

可参考这个[提供了更多示例的项目](https://github.com/mgifford/ACCESSIBILITY.md/tree/main)。

## 结语:你的一小步,用户体验的一大步

这些步骤看起来可能很基础,但它们能大幅提升项目的无障碍程度。你所做的每一个修复——无论是补上一个缺失的标签、消除一个键盘焦点陷阱,还是为视频加上字幕——都会为一位此前无法使用你项目的用户打开一扇门。

无障碍不是一次性的修复,而是一项持续的实践,你不需要一次性做完所有事情。从键盘导航和语义结构开始,保持改动小步进行,并尽早寻求评审。

你今天投入的这些努力,意味着会有更多人能够从你构建的成果中学习、为之贡献,并依赖它。这份收获值得庆祝。

## 贡献者

### 非常感谢所有为本指南分享经验和建议的维护者!

本指南由 [@mlama007](https://github.com/mlama007) 撰写,并有以下贡献者参与:[@ericwbailey](https://github.com/ericwbailey)、[@andyfeller](https://github.com/andyfeller)、[@mgifford](https://github.com/mgifford)、[@smockle](https://github.com/smockle) 和 [@weboverhauls](https://github.com/weboverhauls)
6 changes: 6 additions & 0 deletions _includes/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
{% assign locales = site.data.locales | sort %}
{% for locale in locales %}
{% assign lang = locale[0] %}
{% if page.layout == 'article' and lang != page.lang %}
{% assign translated_article = site.articles | where: 'lang', lang | where: 'class', page.class | first %}
{% unless translated_article %}
{% continue %}
{% endunless %}
{% endif %}
{% assign page_lang_slash = page.lang | append: '/' | prepend: '/' %}
{% assign default_url = page.url | replace: page_lang_slash, '/' %}
{% if lang == "en" %}
Expand Down
6 changes: 6 additions & 0 deletions _includes/nav.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
{% for locale in locales %}
{% assign lang = locale[0] %}
{% assign locale_name = locale[1][lang].locale_name %}
{% if page.layout == 'article' and lang != page.lang %}
{% assign translated_article = site.articles | where: 'lang', lang | where: 'class', page.class | first %}
{% unless translated_article %}
{% continue %}
{% endunless %}
{% endif %}
{% if page.lang == lang %}
<option value="{{ lang }}" selected="selected">{{ locale_name }}</option>
{% else %}
Expand Down