前端可访问性:别让网站成为某些人的禁区
为什么这很重要
在公共建筑中,无障碍通道是标配。但在 Web 世界里,很多开发者依然忽略了这一点。如果一个网站只有健全人能顺畅使用,那它本质上是不完整的。
最近审查过一个项目,按钮没有焦点状态,表单缺少标签,屏幕阅读器根本无法工作。这不仅仅是体验问题,更是合规与道德问题。我们是在做产品,不是在做密室逃脱。
常见误区示例
看看这段代码,虽然功能上能跑,但对辅助技术来说简直是灾难:
// 反面教材:忽略可访问性
function App() {
return (
<div>
<h1>我的网站</h1>
<div>
<input type="text" placeholder="用户名" />
<input type="password" placeholder="密码" />
<button>登录</button>
</div>
<div>
<img src="logo.png" />
<a href="#">关于我们</a>
<a href="#">联系我们</a>
</div>
</div>
);
}
export default App;
这里的问题很明显:图片没有 alt 属性,输入框没有关联的 label,导航结构混乱。屏幕阅读器读到这里只会报出一堆无意义的信息。
正确姿势:从语义化开始
1. 语义化 HTML
语义化标签不仅是给开发者看的,更是给机器读的。header, main, footer, nav 这些标签能直接告诉辅助技术页面的结构。
// 正确姿势:语义化 HTML
function App() {
return (
<div>
<header>
<h1>我的网站</h1>
</header>
<main>
<form>
<div>
<label htmlFor="username">用户名</label>
<input type="text" id="username" aria-label="用户名" />
</div>
<div>
<label htmlFor="password">密码</label>
<input type="password" id="password" aria-label="密码" />
</div>
<button type="submit" aria-label="登录">登录</button>
</form>
</main>
<footer>
<img src="logo.png" alt="网站 logo" />
<nav>
<ul>
<li><a href="#">关于我们</a></li>
<li><a href="#">联系我们</a></li>
</ul>
</nav>
</footer>
</div>
);
}
export default App;
注意 label 的 htmlFor 必须匹配 input 的 id,这样点击文字也能聚焦输入框。图片必须提供有意义的 alt 文本。
2. ARIA 标签的使用
当原生 HTML 无法满足需求时,ARIA(Accessible Rich Internet Applications)是补充手段。比如动态展开的菜单,需要告知用户当前状态。
// 正确姿势:使用 ARIA 标签
function App() {
const [expanded, setExpanded] = React.useState(false);
return (
<div>
<button
aria-expanded={expanded}
aria-controls="menu"
onClick={() => setExpanded(!expanded)}
>
菜单
</button>
<nav aria-hidden={!expanded} id="menu">
<ul>
<li><a href="#">首页</a></li>
<li><a href="#">关于我们</a></li>
<li><a href="#">联系我们</a></li>
</ul>
</nav>
</div>
);
}
export default App;
这里的关键是 aria-expanded 和 aria-controls,它们建立了按钮与菜单之间的逻辑关系。配合 aria-hidden 可以在菜单隐藏时将其从屏幕阅读器中移除。
3. 键盘导航支持
很多用户无法使用鼠标,完全依赖键盘操作。如果 Tab 键跳不过去,或者方向键没反应,这部分用户就被拒之门外了。
// 正确姿势:键盘导航
function App() {
const [focused, setFocused] = React.useState(0);
const items = ['首页', '关于我们', '联系我们', '服务'];
const handleKeyDown = (e) => {
if (e.key === 'ArrowRight') {
setFocused((prev) => (prev + 1) % items.length);
} else if (e.key === 'ArrowLeft') {
setFocused((prev) => (prev - 1 + items.length) % items.length);
}
};
return (
<nav onKeyDown={handleKeyDown} tabIndex={0}>
<ul>
{items.map((item, index) => (
<li key={index}>
<a
href="#"
tabIndex={-1}
style={{
outline: index === focused ? '2px solid blue' : 'none',
backgroundColor: index === focused ? '#e0e0e0' : 'transparent'
}}
onClick={() => setFocused(index)}
>
{item}
</a>
</li>
))}
</ul>
</nav>
);
}
export default App;
这里通过监听 keydown 事件实现了左右方向键切换焦点。tabIndex={-1} 确保列表项不会干扰正常的 Tab 顺序,但可以通过 JS 主动聚焦。视觉上的高亮反馈也很重要,让键盘用户知道当前在哪。
4. 颜色对比度
色盲或视力不佳的用户对颜色非常敏感。WCAG 标准建议正文文本与背景的对比度至少达到 4.5:1。
/* 正确姿势:颜色对比度 */
/* styles.css */
body {
color: #333333; /* 深色文本 */
background-color: #ffffff; /* 浅色背景 */
}
a {
color: #0066cc; /* 蓝色链接,对比度高 */
text-decoration: none;
}
a:hover {
color: #004499;
text-decoration: underline;
}
button {
background-color: #0066cc;
color: #ffffff;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #004499;
}
不要仅靠颜色来传递信息(比如错误提示),最好加上图标或文字说明。上面的配色方案保证了足够的对比度,同时保持了视觉舒适度。
结语
可访问性不是锦上添花,而是基础建设。它关乎公平,也关乎法律风险。花一点时间检查你的组件,确保每个人都能在你的网站上找到路。

