视觉隐藏与语义隐藏是两件事
别名: visually hidden · aria-hidden · inert · sr-only · 视觉隐藏 · 语义隐藏
概念解释
把一块界面从画面上拿掉,和把它从辅助技术的世界里拿掉,是两套独立的开关。视觉隐藏(visually hidden)只改绘制:裁剪、缩到一像素、透明、移出视口,节点仍留在无障碍树里,名称仍可被计算。语义隐藏(semantic hiding)改的是暴露:display: none、visibility: hidden、hidden、aria-hidden、inert 把节点从树里移除或标成不可用。看不见推不出读不着,读不着也推不出看不见。
常被当成同一操作的,是「藏起来」这一个动词。对读屏用户,藏的是哪一层完全不同:sr-only 的标签是故意只留给他们的;aria-hidden="true" 盖在可见按钮上,则是他们失去了视力用户还看得见的东西。
机制
绘制管道和无障碍树的输入不同。透明度、裁剪、transform 出屏只影响合成,平台 API 仍能拿到这个节点,屏幕阅读器会读,Tab 仍可能停在上面(除非再关掉可聚焦)。相反,display: none 与 hidden 从渲染和树上一起拿掉;visibility: hidden 通常也不暴露;aria-hidden 只从树上拿掉,画面可以还在;inert 让整棵子树既不可聚焦也不可点,语义上等于这块不可用。
两套开关正交,所以组合会打出四种结果:看得见也读得着(普通控件)、看得见却读不着(aria-hidden 盖在可见 UI 上)、看不见却读得着(sr-only 标签,这是补名称的正当手段)、看不见也读不着(真正关闭的面板)。出错几乎都是用错了组合:用视觉手段藏错误提示,读屏永远听不到;用 aria-hidden 藏可见的关闭按钮,键盘用户还能聚焦到一块「不存在」的控件。
边界
离屏定位的广告、验证码或不该读的重复装饰,视觉隐藏会把它们留给读屏,这时该走语义隐藏或干脆不渲染。打印样式、opacity: 0 的入场动画在动画中途节点仍暴露,若内容还没准备好被读,需要额外的语义开关,不能假定「暂时看不见」等于「暂时不读」。inert 与 aria-hidden 叠在已聚焦节点上,焦点可能被困在一块辅助技术已经不可见的区域,这是焦点问题,不能单靠隐藏属性解决。搜索引擎和复制粘贴走的也不是无障碍树,语义隐藏不会从 DOM 里删掉文本。
怎么落地
- 需要补 accessible name、但不想占画面的文字,用只影响绘制的 sr-only / clip 技巧,不要
display: none或aria-hidden。 - 对用户应完全不可用的整块 UI(关着的弹层背后的页面、未激活的选项卡面板),用语义隐藏或
inert,不要只靠透明遮罩。 - 禁止在仍然可见、仍可点击的控件上设
aria-hidden="true";禁止把错误、状态和名称所依赖的文本仅用视觉方式藏掉。 - 验证:对同一块 UI 分别看画面和无障碍树。列出四种组合里实际落在哪一种;再用 Tab 确认「读不着」的节点不可聚焦,「读得着但看不见」的节点正是你打算留给辅助技术的那几个。