如何实现可访问导航的子菜单点击切换(无需硬编码ID)
解决方案:无需ID的通用子菜单切换 + 无障碍优化
不用硬编码ID,更不用动态生成data-id,直接靠DOM层级关系就能精准定位当前按钮对应的子菜单,同时给你补上关键的无障碍优化点:
一、核心实现:通用点击处理函数
替换你原有的JS,用DOM遍历替代全局选择,同时补上键盘支持:
// 页面加载完成后绑定事件,别用内联onclick,维护起来更方便 document.addEventListener('DOMContentLoaded', function() { const toggleButtons = document.querySelectorAll('.menu-item button'); toggleButtons.forEach(button => { button.addEventListener('click', handleSubmenuToggle); // 支持键盘回车/空格触发,符合无障碍规范 button.addEventListener('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleSubmenuToggle.call(this); } }); }); }); function handleSubmenuToggle() { // 找到当前按钮所在的父菜单容器 const parentMenuItem = this.closest('.menu-item'); // 定位该容器下的子菜单 const submenu = parentMenuItem.querySelector('.sub-menu-items'); // 切换显示状态 submenu.classList.toggle('show'); // 更新无障碍属性,告诉屏幕阅读器菜单状态 const isExpanded = this.getAttribute('aria-expanded') === 'true'; this.setAttribute('aria-expanded', !isExpanded); }
二、HTML无障碍修正
你的原HTML有几个语义化问题,调整后更符合无障碍标准:
<header> <nav aria-label="Empower Reservation Navigation" class="nav-menu-container"> <ul class="menu-items"> <li class="menu-item slds-is-relative"> <!-- 用aria-expanded替代aria-pressed,语义更准确;aria-haspopup告诉屏幕阅读器这是弹出菜单 --> <button aria-expanded="false" aria-haspopup="true">Menu Item 1 <i class="fa-solid fa-chevron-down"></i></button> <!-- 子菜单隐藏时设aria-hidden="true",屏幕阅读器会忽略 --> <ul class="sub-menu-items" aria-hidden="true"> <li><a href="#">Sub Menu Item</a></li> <li><a href="#">Sub Menu Item 2</a></li> <li><a href="#">Sub Menu Item 3</a></li> <li><a href="#">Sub Menu Item 4</a></li> </ul> </li> <li class="menu-item slds-is-relative"> <button aria-expanded="false" aria-haspopup="true">Menu Item 2<i class="fa-solid fa-chevron-down"></i></button> <ul class="sub-menu-items" aria-hidden="true"> <li><a href="#">Sub Menu Item</a></li> <li><a href="#">Sub Menu Item 2</a></li> <li><a href="#">Sub Menu Item 3</a></li> <li><a href="#">Sub Menu Item 4</a></li> </ul> </li> <li class="menu-item slds-is-relative"> <button aria-expanded="false" aria-haspopup="true">Tools <i class="fa-solid fa-chevron-down"></i></button> <ul class="sub-menu-items" aria-hidden="true"> <li><a href="#">Sub Menu Item</a></li> <li><a href="#">Sub Menu Item 2</a></li> <li><a href="#">Sub Menu Item 3</a></li> <li><a href="#">Sub Menu Item 4</a></li> </ul> </li> <li class="menu-item slds-is-relative"> <button aria-expanded="false" aria-haspopup="true">Contact [Name] <i class="fa-solid fa-chevron-down"></i></button> <ul class="sub-menu-items" aria-hidden="true"> <li><a href="#">Sub Menu Item</a></li> <li><a href="#">Sub Menu Item 2</a></li> <li><a href="#">Sub Menu Item 3</a></li> <li><a href="#">Sub Menu Item 4</a></li> </ul> </li> </ul> </nav> </header>
三、CSS微调(配合无障碍)
补上聚焦样式和细节优化,让键盘导航用户也能清晰感知:
.nav-menu-container { background: black; color: white; font-size: 0.875rem; padding: 1.3125rem 1.875rem; } .menu-item { display: inline-block; /* 改成inline-block,避免inline元素的定位bug */ padding: 0 0.75rem; position: relative; } .menu-item button { background-color: transparent; border: none; color: white; cursor: pointer; /* 明确鼠标指针样式,告知用户可点击 */ padding: 0.5rem 0; } /* 按钮聚焦样式,键盘导航必须可见 */ .menu-item button:focus { outline: 2px solid #fff; outline-offset: 2px; } .sub-menu-items { background: white; display: none; left: -25px; min-width: 183px; padding: 10px 0; position: absolute; top: 40px; } .sub-menu-items.show { display: block; } .sub-menu-items:after { width: 0; height: 0; border-left: 15px solid transparent; border-right: 15px solid transparent; border-bottom: 15px solid white; content: " "; position: absolute; top: -10px; left: 42%; } .sub-menu-items a { color: black; text-decoration: none; padding: .5rem 1rem; display: block; } /* 链接也要加聚焦样式 */ .sub-menu-items a:hover, .sub-menu-items a:focus { background-color: lightgray; }
四、关键无障碍优化总结
- 用
aria-expanded替代aria-pressed:专门用于指示折叠控件的展开状态,语义更准确 - 添加
aria-haspopup="true":告诉屏幕阅读器这个按钮会弹出下拉菜单 aria-hidden控制子菜单可见性:隐藏时设为true,屏幕阅读器会忽略,避免干扰- 键盘支持:确保按钮可通过回车/空格触发,同时添加清晰的聚焦样式
- 移除内联事件:避免HTML与JS耦合,维护更方便
关于ID的疑问
完全不需要用ID或者动态生成data-id,利用closest()和querySelector()的DOM层级遍历,就能精准定位到当前按钮对应的子菜单,代码更简洁,还能避免ID冲突的问题。
内容的提问来源于stack exchange,提问作者Fawn
相关产品推荐
相关产品推荐

