aside标签本身不支持收起展开,必须用details或js补全;它只是语义容器,无交互能力;display: none会彻底切断无障碍访问;真正可访问方案仅有details(轻量静态)或button+js(动态自定义)。

aside标签本身不支持收起展开,必须用或JS补全
HTML 没有原生“可折叠侧边栏”语义标签。aside只是语义容器,不带任何交互能力;display: none纯视觉隐藏,键盘焦点跳不过、屏幕阅读器读不到、SEO 不识别。真正能兼顾可访问性的方案只有两个:details(推荐用于静态/轻量场景)或 button + JS(用于需要自定义动画、状态同步的动态侧边栏)。
常见错误是把 aside 包在 div 里再加 onclick,结果 Tab 键无法聚焦、Enter 键无响应、NVDA 完全静默——这不是“没加 ARIA”,而是根本没提供可交互结构。
-
details必须配对使用:<details><summary>侧边栏标题</summary><aside>...</aside></details>,summary是唯一合法第一个子元素,删掉就失去键盘支持 - 若用 JS 控制显隐,触发按钮必须是
<button type="button"></button>,不能是<div role="button"> —— 后者要手动补 <code>tabindex="0"、keydown监听、aria-expanded同步,漏一项就断链 - 收起后整个
aside区域应设aria-hidden="true",但仅限 DOM 存在且不可见时;若直接移除节点,需确保焦点能安全返回触发按钮 - 响应式切换时,小屏下把侧边栏收到底部,必须同步调整 DOM 位置:把它从
main后面移到main结束标签之后,而不是只改 CSS - 绝对定位的
aside若必须保留,得配合aria-hidden="true"+inert属性临时屏蔽非当前视口区域,并用 JS 在切换后恢复焦点和可访问性状态 -
aside只能是body、article或section的直接子元素;塞进header或footer里会破坏语义边界,读屏器可能误判为导航或版权信息 - 按钮初始状态必须匹配内容真实可见性:
aria-expanded="true"对应内容已显示,反之亦然 - 每次点击后,必须同步操作三件事:切换内容区的
hidden属性(或max-height)、更新按钮的aria-expanded、更新aria-label(如“收起侧边栏”/“展开侧边栏”) - 避免用
aria-hidden控制侧边栏显隐——它会彻底屏蔽整个区域,包括里面本该可聚焦的input或button;应该用hidden属性或 CSSvisibility: hidden配合inert - 检查侧边栏内所有可交互元素的
tabindex值:不能是-1(除非明确禁用),也不能大于 0(打乱自然顺序) - 若侧边栏含搜索框,必须配
<label for="search-id"></label>或aria-labelledby,不能只靠占位符文本 - 广告类
aside必须显式声明role="region"和aria-label="广告区域",否则老旧读屏器(如 IE11 + NVDA)可能完全忽略
DOM顺序必须与视觉顺序一致,否则键盘流和读屏器逻辑断裂
用 flex 或 grid 把 aside 视觉上放到右侧,但 DOM 里写在 main 后面,会导致屏幕阅读器先读正文、再读侧边栏——这还勉强合理;但如果 DOM 里 aside 在 main 前面,视觉上却靠 order 或 transform “挪”到右边,键盘用户就会先听到一堆“相关链接”“广告推荐”,再听正文,体验完全割裂。
更隐蔽的问题:用 position: absolute 或 transform: translateX() 移动 aside,DOM 顺序没变,辅助技术仍按原始顺序播报,和视觉流彻底脱钩。
收起/展开状态必须由 aria-expanded + JS 双向同步
aria-expanded 不是装饰属性,它是屏幕阅读器判断“这个按钮控制的内容是否可见”的唯一依据。只在按钮上写 aria-expanded="false",却不随 JS 状态更新,等于告诉辅助技术“它永远关着”。同样,JS 改了 open 状态但没更新 aria-expanded,读屏器就完全不知道发生了什么。
侧边栏内可操作元素必须保持可聚焦,且 tabindex 不被覆盖
动态插入的侧边栏(比如 JS 加载的广告或筛选面板),如果内部有 <input>、<button></button> 或带 tabindex="0" 的元素,但父级 aside 被设了 tabindex="-1" 或 inert,这些元素就全部失焦。更常见的是框架组件自动加了 tabindex="-1" 到容器上,导致内部按钮 Tab 不进去。
aria-expanded 没更新,或一次 inert 设置时机不对,就足以让键盘用户卡在空白页上。











