多列大菜单必须用嵌套 ul 结构,主菜单项为 li,子菜单为该 li 的直接子 ul;需配合 aria-haspopup、aria-expanded、position: relative 等属性与 grid 布局,确保可访问性与响应式正确。

多列大菜单的 HTML 结构必须用嵌套 ul,不能靠 div 拼列
纯视觉上“分三列”不等于结构上可以乱套。很多开发者用 div 包三组 a,再用 CSS Grid 或 float 排成三列——这会导致键盘用户 Tab 一次就跳过整块菜单,屏幕阅读器读作“横幅区域,含 3 个链接”,二级项完全不可达。
真正起作用的是 DOM 层级 + ARIA 协同:
- 主菜单项(如“产品”)必须是
li - 它的子菜单容器必须是该
li的直接子元素ul,不能包在div里 - 这个子
ul内部再放多个li,每个li可再嵌ul实现三级 - 所有可展开项(如“产品”链接)必须带
aria-haspopup="menu"和aria-expanded="false"
否则,哪怕样式再像“大菜单”,对残障用户和 SEO 来说就是一块黑盒。
display: grid 是实现多列布局最稳的方式,但要配 grid-template-columns 和 minmax()
float 和 inline-block 在多列大菜单里极易因换行、空格、基线偏移导致错位;flex 虽好,但默认单行,要撑多列得手动算 flex-basis,响应式时难控制。
立即学习“前端免费学习笔记(深入)”;
grid 更自然:
- 父
ul设display: grid - 用
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))实现“最少 200px 宽,最多均分” - 子
li自动填入各列,无需额外 wrapper - 列数随容器宽度自动增减,PC 上 4 列,平板上 2 列,手机上退化为单列垂直流
注意:别给子 li 设 width 或 flex,会干扰 grid 自动分配;也别用 grid-column: span 2 强制跨列——大菜单讲究信息平权,每列应承载同等权重内容。
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
悬停失效?大概率是 position: absolute 没配 position: relative 在父 li 上
多列大菜单的子菜单常靠 position: absolute 浮出,但浏览器定位逻辑很严格:
- 绝对定位元素往上找第一个
position: relative/absolute/fixed的祖先 - 如果父
li没设position: relative,它就会跳到nav或body上,导致子菜单飞到页面左上角或错位几十像素
正确做法:
- 每个含下拉的
li都加position: relative - 子
ul设position: absolute; top: 100%; left: 0;(向下展开)或left: 100%(向右展开) - 避免用
margin-top或transform微调位置——那只是掩盖定位错误
另外,:hover 在移动端基本无效,真要支持触屏,得用 JS 监听 click 并切换 aria-expanded 和显隐类。
移动端怎么收拢?别只靠 display: none,要同步管理 aria-hidden 和焦点流
很多代码在媒体查询里直接写 .submenu { display: none },结果问题一堆:
- 屏幕阅读器仍会读出隐藏菜单里的链接(
display: none不影响可访问性树) - 键盘用户按 Tab 仍会聚焦到隐藏项上(焦点未被拦截)
正确关闭方式:
- 用
visibility: hidden+aria-hidden="true"组合 - 同时在 JS 中监听菜单按钮点击,用
focus()把焦点移到第一个可见菜单项 - 收起时,用
el.setAttribute('aria-expanded', 'false')并确保对应ul的aria-hidden同步更新
复杂点在于:多列大菜单一旦展开,高度常超视口,得加 max-height + overflow-y: auto,但滚动容器本身也要能接收键盘焦点(加 tabindex="0"),否则方向键无法滚动。
这不是“做完就能上线”的功能,是每层 DOM、每个属性、每次交互都得对齐规范的活。


















