纯 css 实现侧边菜单层级缩进应使用嵌套 结构,通过 ul ul 选择器统一设置 padding-inline-start: 24px(兼顾 rtl),配合 max-height 动画控制展开,确保语义嵌套、无障碍支持及键盘导航一致性。

如何用 CSS 实现侧边菜单的层级缩进(非 JS 展开逻辑)
纯 CSS 控制缩进,核心是利用 padding-left 或 margin-left 配合嵌套结构,而非依赖 JavaScript 控制显示/隐藏。后台系统中常见多级菜单(如「系统管理 > 角色管理 > 权限分配」),缩进必须视觉清晰、语义明确、支持键盘焦点导航。
关键点:缩进量要可预测、可维护;不能靠写死多个 class(如 .level-1、.level-2)硬编码,否则新增层级就得改 CSS 和 HTML。
- 推荐用嵌套
<ul></ul>+<li>结构,每层子菜单作为上一级<li>的最后一个子元素 - 用 CSS 选择器
ul ul或li > ul匹配子级,统一加padding-left: 24px(不建议用margin,易受外边距折叠干扰) - 避免对
<a></a>单独设text-indent——它只缩进首行,且对 inline 元素行为不稳定 - 若需支持 RTL(从右向左排版),改用
padding-inline-start替代padding-left,现代浏览器兼容性足够
点击展开时保持缩进对齐(避免子菜单“跳位”)
很多后台菜单点击后子菜单突然出现,导致父项文字位置偏移——本质是子 <ul></ul> 默认 display: block 占据空间,而缩进样式没提前预留位置。
- 给所有子级
<ul></ul>设display: block+max-height: 0+overflow: hidden,再配合transition: max-height .2s ease-in-out - 缩进样式(如
padding-left)必须作用在父<li>或子<ul></ul>上,不能只加在<a></a>上——否则max-height动画时文字会“抽搐” - 别用
height: 0做动画:IE 不支持,且子元素有 border/padding 时高度计算不准;max-height更稳妥(设一个足够大的值,如max-height: 500px) - 如果用
display: none切换,缩进本身不会变,但切换瞬间无过渡,用户体验生硬,且无法做键盘 focus 管理
Vue/React 中动态渲染多级菜单时的缩进陷阱
组件化渲染时,容易把缩进逻辑塞进 JS 层(比如根据 depth 字段动态加 class),这会让样式和数据耦合,也难以复用。
- 保持 HTML 结构扁平嵌套:即使数据是平面数组(带
parentId),渲染时仍应组装成嵌套的<ul><li></ul>,而不是靠v-for或map拼一堆同级<li>+ 内联style="padding-left: ${depth * 24}px" - 避免在 JSX/模板里写
style={{ paddingLeft: `${depth * 24}px` }}——CSS-in-JS 会丢失伪类(如:hover缩进下的背景色变化)、媒体查询响应能力弱 - 若必须用深度变量(如为了 aria-level 属性),只用于
aria-level或data-depth,缩进仍交由 CSS 的ul ul规则处理 - 注意 SSR 场景:服务端渲染时若子菜单默认收起,
max-height: 0可能导致子项不可见,需配合visibility: hidden或初始状态一致性处理
无障碍与键盘导航下缩进的隐含要求
缩进不只是视觉问题,屏幕阅读器会结合 DOM 层级理解菜单结构。错误的缩进方式会导致「角色管理」被读成独立项,而非「系统管理」的子项。
- 必须用语义化嵌套:
<nav aria-label="主菜单"><ul><li> <a>系统管理</a><ul><li><a>角色管理</a></li></ul> </li></ul></nav>,不能用<div> 模拟列表<li>缩进本身不影响 ARIA,但若用 <code>position: absolute偏移子菜单位置,可能脱离流式布局,导致焦点顺序错乱 - 键盘 Tab 进入菜单后,用方向键切换应遵循 DOM 顺序:父项 → 子项(即使子菜单当前收起,也要确保
tabindex="-1"合理设置) - 不要仅靠缩进表达层级——必须配合
aria-haspopup="true"、aria-expanded和aria-controls,否则视障用户无法感知可展开状态
缩进看着简单,真正卡住人的往往是 DOM 结构是否真实嵌套、CSS 是否与可访问性要求共存、以及动画过程中尺寸计算是否稳定——这三处出问题,轻则视觉错位,重则键盘或读屏失效。











