可访问性 — CodeRoadMapHTML 可访问性
这一课你将能做到
- 为所有图片写有意义的 alt 文本,区分内容图、装饰图和功能图的 alt 策略
- 实现键盘完全可操作的页面流程:正确的 Tab 顺序、可见的焦点指示器、弹窗焦点管理
- 为自定义组件添加合适的 ARIA 角色和属性,知道什么时候该用原生 HTML 代替 ARIA
为什么要学
可访问性(Accessibility,简称 a11y)不是「锦上添花」,是 Web 的底线。中国视障者约 1700 万,全球约 2.85 亿;还有色盲、运动障碍、认知障碍用户。你的页面如果全靠鼠标、靠颜色传达信息、靠图片承载关键内容——这些人就被拒之门外。而且,良好的可访问性直接利好 SEO:搜索引擎爬虫本质上就是「盲人用户」。
人话:可访问性就像建筑的无障碍坡道。你不是为「特定人群」做的,你做的是所有人都能用的基础设施。
先建立图像
可访问性四大支柱:
可感知(Perceivable)
├── 图片有 alt,视频有字幕
├── 颜色不是唯一信息载体
└── 文字可放大 200% 不溢出
可操作(Operable)
├── 全部功能可用键盘完成
├── 焦点可见且顺序合理
└── 无时间限制的内容(或可控)
可理解(Understandable)
├── 页面语言声明正确
├── 错误提示清晰
└── 导航一致
健壮性(Robust)
├── 标准 HTML 语义
├── ARIA 状态正确更新
└── 兼容辅助技术
跟着做
步骤 1:图片 alt 策略
新建 a11y.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>可访问性演示</title>
</head>
<body>
<!-- 内容图:alt 描述图片传达的信息 -->
<img src="chart.png" alt="2026年Q3销售额柱状图:Q1 80万,Q2 95万,Q3 120万,呈上升趋势">
<!-- 装饰图:空 alt,屏幕阅读器跳过 -->
<img src="divider-line.png" alt="">
<!-- 功能图(图标按钮):alt 描述功能 -->
<button>
<img src="icon-search.png" alt="搜索">
</button>
<!-- 复杂图表:简短 alt + 正文详细描述 -->
<img src="complex-diagram.png" alt="系统架构图,详见下文描述">
<p>本系统采用三层架构:表现层使用 Vue.js……</p>
</body>
</html>
alt 决策树:
这张图承载信息?
├── 是 → alt 描述关键信息
│ └── 信息复杂? → 简短 alt + 正文/figcaption 详述
└── 否 → 纯装饰?
├── 是 → alt=""
└── 否 → 功能图标? → alt 描述功能(非外观)
步骤 2:键盘导航与焦点管理
<style>
/* 焦点指示器必须可见 */
*:focus {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* 自定义按钮也要有焦点样式 */
.btn {
padding: 8px 16px;
border: 1px solid #333;
cursor: pointer;
}
.btn:focus {
outline: 2px solid #005fcc;
}
</style>
<!-- 原生控件自动支持键盘 -->
<button>原生按钮(Tab 可达,Enter/Space 激活)</button>
<a href="/page">原生链接(Tab 可达,Enter 跳转)</a>
<input type="text" placeholder="输入框(Tab 可达)">
<!-- 自定义控件:必须手动补全键盘支持 -->
<div class="btn" role="button" tabindex="0"
onclick="handleClick()"
onkeydown="handleKey(event)">
自定义按钮
</div>
<script>
function handleClick() {
console.log('点击');
}
function handleKey(event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleClick();
}
}
</script>
- Tab 键能访问所有交互元素
- Shift+Tab 反向导航
- Enter/Space 激活按钮,Enter 跳转链接
- Esc 关闭弹窗、下拉菜单
- 焦点顺序与视觉顺序一致(避免正 tabindex)
步骤 3:弹窗焦点陷阱
<button onclick="openDialog()">打开弹窗</button>
<div id="dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title"
style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
border:1px solid #333; padding:20px; background:#fff;">
<h2 id="dialog-title">确认删除</h2>
<p>此操作不可撤销,是否继续?</p>
<button onclick="confirmDelete()">确认</button>
<button onclick="closeDialog()">取消</button>
</div>
<script>
let lastFocusedElement;
function openDialog() {
lastFocusedElement = document.activeElement;
const dialog = document.getElementById('dialog');
dialog.style.display = 'block';
dialog.querySelector('button').focus(); // 焦点移入弹窗
trapFocus(dialog);
}
function closeDialog() {
const dialog = document.getElementById('dialog');
dialog.style.display = 'none';
lastFocusedElement.focus(); // 焦点返回触发元素
}
function trapFocus(dialog) {
const focusable = dialog.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
const first = focusable[0];
const last = focusable[focusable.length - 1];
dialog.addEventListener('keydown', function(e) {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
if (e.key === 'Escape') {
closeDialog();
}
});
}
</script>
- 打开时焦点移入弹窗内第一个可交互元素
- Tab 在弹窗内循环,不跑到弹窗外
- 关闭时焦点回到触发弹窗的元素
步骤 4:色彩对比与信息载体
<style>
/* 失败:颜色是唯一错误提示方式 -->
.error { color: #ff0000; } /* 色盲用户看不出 */
/* 成功:颜色 + 图标 + 文字 -->
.error {
color: #d32f2f;
border-left: 4px solid #d32f2f;
padding-left: 12px;
}
.error::before {
content: "⚠ ";
}
</style>
<form>
<label for="email">邮箱</label>
<input type="email" id="email" name="email" aria-required="true" aria-invalid="false" aria-describedby="email-error">
<p id="email-error" style="display:none;" class="error">邮箱格式不正确,示例:name@example.com</p>
</form>
<script>
const email = document.getElementById('email');
const error = document.getElementById('email-error');
email.addEventListener('blur', function() {
if (!email.validity.valid) {
email.setAttribute('aria-invalid', 'true');
error.style.display = 'block';
} else {
email.setAttribute('aria-invalid', 'false');
error.style.display = 'none';
}
});
</script>
- AA 级:正文文字对比度 ≥ 4.5:1,大文字 ≥ 3:1
- AAA 级:正文文字对比度 ≥ 7:1,大文字 ≥ 4.5:1
- 工具:WebAIM Contrast Checker、Chrome DevTools 颜色拾取器
步骤 5:屏幕阅读器基础
NVDA(Windows 免费)、VoiceOver(Mac/iOS 自带)、TalkBack(Android 自带)是三大主流屏幕阅读器。测试要点:
<nav aria-label="主导航">
<ul>
<li><a href="/" aria-current="page">首页</a></li>
<li><a href="/articles">文章</a></li>
</ul>
</nav>
<main>
<h1>文章列表</h1>
<article>
<h2><a href="/post/1">HTML 可访问性指南</a></h2>
<p>本文介绍如何构建无障碍网页……</p>
<time datetime="2026-10-06">2026年10月6日</time>
</article>
</main>
aria-label:给没有可见文本的区域命名(如导航、搜索框)
aria-current="page":告诉用户「这是当前页」
- 语义化标签(nav、main、article)让屏幕阅读器提供地标导航快捷键
讲清楚要点
- alt 不是「图片描述」,是「图片功能替代」:装饰图 alt="",内容图 alt="信息摘要",功能图 alt="功能说明」。不要写「图片」「image」这种无信息量词。
- 键盘可访问是底线,不是高级功能:所有鼠标能做的事,键盘必须能做。自定义组件要补 tabindex、keydown、focus 样式。
- ARIA 补丁原则:能用
<button> 就不用 role="button";能用 <nav> 就不用 role="navigation"。原生语义自带键盘支持和焦点管理,ARIA 需要全手动实现。
- 颜色不是唯一信息载体:错误提示不能只变红,要加图标、文字、边框。约 8% 男性有色觉异常。
- 焦点管理是弹窗的生命线:打开移入、关闭移回、Tab 陷阱、Esc 关闭——漏一个,键盘用户就困在弹窗里或找不到回去的路。
你可能会懵的地方
-
aria-label 和 aria-labelledby 什么区别?
aria-label 直接写文本值;aria-labelledby 引用页面上其他元素的 id,适合「标签在别处」的场景(如搜索框在 header,label 在 sidebar)。
-
hidden 和 aria-hidden 什么区别?
hidden 是 HTML 属性,元素完全从 DOM 中移除渲染和辅助技术访问;aria-hidden="true" 只是对屏幕阅读器隐藏,视觉仍可见。不要对可聚焦元素加 aria-hidden。
-
为什么 tabindex="-1" 用于弹窗?
弹窗容器本身不需要 Tab 可达,但打开时需要 JS focus() 把焦点移入。tabindex="-1" 让元素可程序聚焦但不在 Tab 序列中。
-
语义化标签和 ARIA 角色重复吗?
不冲突,但冗余。<nav role="navigation"> 里 role="navigation" 是多余的,nav 已隐含该角色。W3C 推荐:如果原生标签已表达语义,不加 ARIA。
-
可访问性只服务视障用户吗?
不是。键盘导航利好所有不方便用鼠标的人(程序员、忙碌中的用户、运动障碍者);高对比度利好老年人;清晰结构利好所有人。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 所有图片 alt="图片" | 无信息量 | 描述内容或功能,装饰图 alt="" |
| 自定义 div 按钮无 tabindex | 键盘无法聚焦 | tabindex="0" + keydown |
| 错误提示只变红 | 色盲用户看不见 | 加图标、文字、边框 |
| 弹窗打开不管理焦点 | 键盘用户不知道弹窗出现 | JS focus() 移入弹窗 |
| 对 button 加 aria-hidden | 屏幕阅读器忽略但键盘仍能聚焦,状态不一致 | 用 hidden 或 disabled |
| 滥用正 tabindex | Tab 顺序混乱 | 按文档自然顺序,必要时 tabindex="0" |
卡点急救
- 焦点样式被 CSS reset 掉了:检查是否有
*:focus { outline: none; }。保留 outline 或换成 box-shadow:outline: 2px solid #005fcc;。
- 弹窗后背景内容仍可 Tab 到:实现 focus trap(见步骤 3),监听 Tab 事件在弹窗首尾循环。
- aria-live 不播报:aria-live 区域必须在页面加载时就存在(不能动态插入)。动态内容更新前确保容器已渲染。
- 屏幕阅读器不读 placeholder:placeholder 不是 label。每个输入框必须有 label(可见或视觉隐藏
class="sr-only")。
- 色盲测试工具:Chrome DevTools → Rendering → Emulate vision deficiencies;或安装 Stark 插件。
面试常问
-
什么是 Web 可访问性(a11y)?
确保 Web 内容对所有用户(包括视障、听障、运动障碍、认知障碍用户)都可感知、可操作、可理解、健壮。WCAG 2.1 是核心标准,分 A/AA/AAA 三级,国内合规通常要求 AA。
-
alt 属性的最佳实践?
内容图:描述图片传达的关键信息;装饰图:alt="";功能图:描述功能(如「搜索」「关闭」);复杂图:简短 alt + 正文详述。避免「图片」「image」开头。
-
如何确保键盘可访问?
① 所有交互元素(链接、按钮、表单)可用 Tab 聚焦;② 自定义组件加 tabindex="0" 和 keydown 事件;③ 焦点顺序与视觉顺序一致;④ 焦点指示器可见;⑤ 弹窗实现焦点陷阱和 Esc 关闭。
-
ARIA 的使用原则?
第一原则:能用原生 HTML 语义就不用 ARIA。ARIA 是补丁,用于自定义控件、动态内容更新、复杂关系补充。错误 ARIA 比不用更糟。
自测三问
- 一张纯装饰性的分割线图片,alt 怎么写?
- 自定义 div 按钮要补哪些属性才能键盘可用?
- 弹窗打开后,焦点管理的三条铁律是什么?
小结一句话
可访问性不是特性,是质量:alt 让信息可感知,键盘让操作可达,ARIA 让语义完整,对比度让所有人看得清——这些不是「额外工作」,是 Web 的应有之义。
延伸