为什么语义化标签是无障碍布局的第一步
HTML5提供了大量语义化标签,比如header、nav、main、aside、footer、article和section。这些标签的意义远不止让代码看起来更整洁,它们实际上会向辅助技术传递结构信息。屏幕阅读器在遇到这些标签时,可以自动识别页面的不同区域,用户能够快速跳转到导航、正文或页脚,而不需要一层一层地听完整页内容。
反过来说,如果整个页面都用div堆出来,哪怕视觉上看起来一模一样,屏幕阅读器用户听到的就是一连串没有含义的内容块,很难判断自己身在何处。很多开发者习惯性地写<div class="header">,这种类名对辅助技术来说完全是透明的,因为它只是一个普通容器。
一个基本的语义化布局骨架大致如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>产品介绍页面</title>
</head>
<body>
<header>
<h1>网站名称</h1>
<nav aria-label="主导航">
<ul>
<li><a href="/">首页</a></li>
<li><a href="/products">产品</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h2>产品详情</h2>
<p>这里是正文内容。</p>
</article>
<aside aria-label="相关推荐">
<h3>推荐阅读</h3>
</aside>
</main>
<footer>
<p>版权信息</p>
</footer>
</body>
</html>注意两个细节:一是html元素上声明了lang属性,屏幕阅读器会根据它选择合适的语音引擎,中文页面如果不声明语言,可能被用英文发音读出来,体验非常糟糕;二是当页面存在多个nav时,用aria-label给每个导航命名,用户就能区分主导航和底部导航。
另外要提醒的是,语义标签不能滥用。section和article都应该有自己的标题,如果一个容器仅仅是为了样式分组,那用div反而是更正确的选择,硬套语义标签只会制造噪音。
键盘可操作性:被视觉设计忽略的死角
无障碍不仅是视障用户的事,还有大量用户因为肢体障碍或者临时受伤,只能依靠键盘操作网页。键盘用户的核心依赖是Tab键在可交互元素之间移动焦点,因此布局代码必须保证两件事:交互元素天然可聚焦,焦点状态清晰可见。
最常见的反例是用div或span加上JavaScript点击事件来模拟按钮。这种元素视觉上像按钮,键盘却无法聚焦到它上面,用户按一百次Tab也碰不到。正确做法是直接使用button元素,如果确实需要用其他标签模拟,至少要补上tabindex和键盘事件监听:
// 不推荐:纯div无法被键盘访问
// 推荐:直接使用语义化元素
<button id="submitBtn">提交</button>
// 如果必须模拟,需要补齐可访问性
const fakeBtn = document.querySelector('.fake-btn');
fakeBtn.setAttribute('tabindex', '0');
fakeBtn.setAttribute('role', 'button');
fakeBtn.addEventListener('keydown', function(e) {
// 空格和回车都应触发按钮行为
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
});第二件事是焦点样式。很多设计稿要求去掉链接和按钮的outline,因为蓝色描边看起来不好看。直接写outline: none会让键盘用户彻底迷失位置,不知道当前焦点在哪个元素上。如果嫌默认样式丑,可以用:focus-visible伪类自定义一个更美观的焦点指示:
.btn:focus {
/* 永远不要彻底移除焦点指示 */
outline: 3px solid #2563eb;
outline-offset: 2px;
}布局结构上还要注意焦点顺序。Tab的移动顺序默认跟随DOM顺序,如果用CSS的order属性或者绝对定位大幅调整视觉顺序,就可能出现视觉上从左到右、焦点却从右到左的错乱。布局时尽量让DOM顺序与视觉顺序和逻辑顺序保持一致,这同时也是对移动端和搜索引擎友好的做法。
此外,页面应该提供跳过导航的机制,也就是常见的跳到主要内容链接。对键盘用户来说,每换一个页面都要把几十个导航链接按一遍是极其痛苦的:
<a href="#main-content" class="skip-link">跳到主要内容</a>
<main id="main-content">
<h1>正文标题</h1>
</main>
<style>
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
left: 0;
top: 0;
background: #fff;
padding: 8px 16px;
z-index: 100;
}
</style>图片、表单与ARIA:补齐最后一块拼图
图片的替代文本是老生常谈但错误率极高的一点。img标签的alt属性不是可选项。有信息量的图片要写简洁准确的描述,纯装饰性图片要写空的alt="",让屏幕阅读器直接跳过。最糟的写法是漏掉alt属性,屏幕阅读器会把图片文件名读出来,用户听到的是img下划线banner点jpg这类毫无意义的字符。
表单是另一个重灾区。输入框必须关联label,仅靠placeholder做标签是不合格的,因为用户开始输入后占位符就消失了,而且很多屏幕阅读器对placeholder的支持并不稳定:
<!-- 推荐写法:label通过for与input的id关联 -->
<label for="username">用户名</label>
<input type="text" id="username" name="username"
autocomplete="username" required>
<!-- 不合格写法:仅用placeholder充当标签 -->
<input type="text" placeholder="请输入用户名">当表单校验出错时,错误信息也要能被辅助技术感知。可以使用aria-describedby把错误提示关联到输入框,或者使用aria-live区域播报动态消息:
<label for="email">邮箱</label> <input type="email" id="email" aria-describedby="emailError"> <span id="emailError" role="alert">请输入有效的邮箱地址</span>
关于ARIA属性,有一条重要原则:能用原生HTML解决的,就不要用ARIA。ARIA本质上是在给元素打补丁,写得不对反而比不写更糟。比如自己用div加role="button"模拟按钮,还不如直接用button元素省事可靠。ARIA更适合用在原生HTML覆盖不到的复杂组件上,例如手风琴、选项卡、模态框,这时候才需要配合role和aria-expanded等属性描述组件状态。
最后,颜色对比度也属于可访问性布局的范畴。正文文字与背景的对比度至少要达到4.5比1,大号文字可以放宽到3比1。只用颜色区分状态(比如红色表示错误)同样有风险,色盲用户可能分辨不出来,最好同时配合图标或文字说明。写完布局后,建议用键盘走一遍完整流程,再用浏览器的无障碍检查工具或Lighthouse跑一次检测,多数低级问题都能被及时抓出来。可访问性不是一次性的任务,而是应该在每次迭代中持续关注的工程习惯。