正确语义结构是用外层容器包裹和主内容,与之同级或为子元素;须嵌套在内容流中,需加role="navigation";侧边栏需设flex-shrink:0和min-width;隐藏时须同步inert、aria-hidden、overflow:hidden及transform。

nav 和 sidebar 的 HTML 语义层级不能平级堆砌
很多人直接把 <nav></nav> 和 <aside></aside> 并列放在 下,结果侧边栏在 flex 布局里被压缩、导航栏文字换行、小屏下错位——根本原因是没建立清晰的容器边界。
正确做法是用一个外层 <div class="layout"> 或 <code><main></main> 包裹全部内容区(含 <nav></nav> 和主内容),再让 <aside class="sidebar"></aside> 与这个容器同级或作为其子元素;<nav></nav> 必须嵌套在内容流中,而不是飘在顶部靠 position: fixed 硬压。
-
<nav></nav>放在主内容区域顶部,语义上属于“当前视图的导航”,不是全站浮层 - 侧边栏用
<aside></aside>,但必须加role="navigation"明确用途,否则部分读屏器会忽略 - 避免
<nav></nav>内部直接写文字链接,每个项必须是<a href="#"></a>,否则键盘 Tab 无法聚焦
Flex 布局中 sidebar 宽度塌陷或撑满的修复方式
设了 width: 240px 还是被压缩?常见于父容器用了 display: flex 但没切断收缩行为。浏览器默认允许 flex 项目收缩(flex-shrink: 1),侧边栏就变成“可牺牲项”。
关键参数只有两个:flex-shrink: 0 + min-width。不要只写 width,它在响应式中断时毫无约束力。
- 给侧边栏根元素加
flex: 0 0 min(240px, 100vw),兼顾固定宽度和移动端安全 - 如果用
position: fixed,必须同步设top: 0和left: 0,Safari 对单边定位支持不一致 - 用
position: sticky替代 fixed 时,确保其父容器有overflow: visible,否则粘性失效
移动端收起侧边栏后焦点与滚动仍可操作的问题
仅用 display: none 或 visibility: hidden 隐藏侧边栏,DOM 还在,Tab 键能跳进去,屏幕阅读器照样播报,背景页还能滚动——这不是隐藏,是障眼法。
真隐藏要四步同步:DOM 属性、CSS 显隐、焦点隔离、滚动锁定。
- 切换时用 JS 设置
el.setAttribute('inert', ''),比 CSS 更彻底地禁用交互 - 配合
aria-hidden="true"告诉辅助技术“别念我” - 侧边栏收起时,给
加overflow: hidden,防止背后内容滑动 - 用
data-collapsed="true"这类自定义属性驱动 CSS,比如[data-collapsed="true"] .sidebar { transform: translateX(-100%); }
图标与文字对齐、悬停反馈、子菜单展开状态的 ARIA 同步
侧边栏里带图标的菜单项常出现文字基线偏移、子菜单点击无反馈、展开后焦点卡住——问题不在样式,而在 ARIA 状态没随 DOM 变化更新。
例如点击“管理”展开子菜单,<li> 必须立刻更新两个属性:aria-expanded 和 aria-haspopup,否则键盘用户不知道该不该按 Enter。
- 图标统一用伪元素
::before实现,避免 img 标签破坏行高和焦点顺序 - 每个可展开项的父
<li>加aria-haspopup="true",初始aria-expanded="false" - 子菜单
<ul></ul>必须紧跟父<li>,不能隔标签,否则屏幕阅读器无法关联 - 悬停色块用
background-color,别用outline覆盖默认焦点环,否则键盘用户看不见当前焦点
<nav></nav> 的焦点顺序、<aside></aside> 的 ARIA 状态、CSS 的渲染层、以及 JS 控制的 inert 属性是否仍然严格对齐——漏掉任意一环,就会有人卡在看不见的按钮上。











