按钮和链接语义不可互换:<button>通告为“button”并响应空格/回车,<a>通告为“link”仅响应Enter且必须有真实跳转目标;混用会破坏可访问性、键盘导航与用户预期。

按钮和链接在屏幕阅读器中被识别为完全不同的角色
屏幕阅读器不会凭外观判断元素用途,而是严格依据 HTML 语义。<button> 被通告为 “button”,<a> 被通告为 “link”——这是不可互换的语义角色。用户依赖这个区别快速决策:点 link 是跳转,点 button 是触发动作(比如弹窗、播放、删除)。混用会导致认知混乱,尤其对视障用户。
常见错误现象:<div role="button"> 或 <a href="#" onclick="doSomething()"> 这类写法,即使加了 role 或 JS,也无法替代原生语义。前者缺少键盘交互支持(空格/回车不触发),后者会让屏幕阅读器误报 “link”,却无实际跳转目标。
-
<button>自带role="button"、可聚焦、默认响应 Space 和 Enter -
<a href="xxx">自带role="link",仅响应 Enter(Space 无效) -
<a href="#">是反模式:它声明自己是 link,但无真实目标,会误导用户并干扰键盘导航流
键盘操作行为不兼容,不能靠 CSS 或 JS 模拟补全
可访问性不是“看起来像就行”。原生 <button> 在 focus 状态下按 Space 触发 click 事件;<a> 在 focus 状态下按 Space 会滚动页面(浏览器默认行为),只有 Enter 才触发跳转。这个差异无法用 preventDefault() 完全对齐——因为屏幕阅读器仍按语义播报,而用户已形成肌肉记忆。
使用场景:表单内提交按钮必须是 <button type="submit">,不能用 <a> + JS 模拟;同理,纯跳转操作(如“返回首页”)不该塞进 <button> 里再用 window.location 实现。
立即学习“前端免费学习笔记(深入)”;
- 用
<button>做跳转 → 违反用户预期,且无法被搜索引擎索引为有效链接 - 用
<a>做非跳转动作 → 缺失button的语义、键盘行为、禁用逻辑(disabled属性对<a>无效) - 两者都需保证焦点可见:不要用
outline: none无替代方案
禁用状态与表单序列化行为完全不同
<button disabled> 会自动从 tab 键导航流中移除、不触发事件、视觉灰化(浏览器默认),且无障碍 API 明确暴露 aria-disabled="true"。而 <a> 没有 disabled 属性,只能靠 CSS + pointer-events: none + 移除 href 模拟,但这无法阻止键盘 focus、不改变语义角色、也不影响屏幕阅读器播报。
性能 / 兼容性影响:现代浏览器对 <button disabled> 的处理高度一致;而模拟禁用的 <a> 在旧版 Safari 或部分辅助技术中可能仍被读作“可点击链接”,导致用户反复尝试。
-
<button disabled>不参与表单提交,也不会出现在FormData中 -
<a>从不参与表单序列化,无论是否加href - 若需“禁用跳转”,正确做法是移除
href并添加aria-disabled="true",同时确保视觉样式明确传达不可用状态
ARIA 不能修复语义错用,只能补救边缘情况
给 <div> 加 role="button" 并不等于 <button>。它只是告诉屏幕阅读器“这玩意儿是按钮”,但不自带 focusable、keyboard event handler、form 关联等能力。你得手动实现所有这些,且极易遗漏细节(比如没处理 Space、没同步 aria-pressed、没管理 focus 状态)。
容易踩的坑:用 <a href="javascript:void(0)"> 替代按钮,或给链接加 aria-role="button" —— 这两类写法都在 WCAG 中被明确定义为失败案例(SC 4.1.2)。
- 优先用原生
<button>或<a>,而不是“增强”非语义元素 - 只有当需要自定义复杂控件(如开关、滑块)时,才考虑 ARIA widget 模式,且必须完整实现 keyboard behavior 和 state 同步
- 纯图标按钮(无文字)必须配
aria-label或aria-labelledby,否则屏幕阅读器只读 “button” 二字,毫无意义



















