下拉菜单必须同时设置 aria-expanded 和 aria-haspopup 属性,aria-expanded 随展开状态动态更新布尔值,aria-haspopup="menu" 加在触发按钮上;焦点需自动管理,打开时聚焦首项、关闭时回归按钮;菜单项须用 role="menuitem" 并确保可聚焦;交互需兼容 hover、键盘(Enter/Space/Escape)及触摸事件,样式仅辅助,逻辑由 JS 实现。

下拉菜单必须有 aria-expanded 和 aria-haspopup
没有这两个属性,屏幕阅读器根本不知道这是个可展开的控件。光靠 hidden 或 opacity-0 不会触发语义识别。你得手动控制它们的值:aria-expanded="true" 对应菜单可见,aria-expanded="false" 对应收起;aria-haspopup="menu" 要加在触发按钮上,不能漏。
常见错误是只加了 aria-expanded 却忘了 aria-haspopup,或者把 aria-expanded 错写成静态 "true"(没随状态更新)。用 React 时别直接写死字符串,要用布尔值绑定:aria-expanded={isOpen}。
焦点管理不能只靠 tabindex
下拉打开后,焦点必须自动落到第一个可聚焦项(通常是第一个 <a></a> 或 <button></button>),关闭后要回到触发按钮。Tailwind 本身不处理这个,得自己写逻辑。
容易踩的坑:
立即学习“前端免费学习笔记(深入)”;
- 用
tabindex="-1"给菜单容器加临时焦点,但没在打开时调用.focus() - 关闭菜单后没把焦点还给按钮,导致键盘用户“掉出”交互流
- 菜单里用了
div当菜单项,却没加tabindex="0"和role="menuitem",结果无法用键盘访问
推荐结构:触发按钮用 role="button",菜单容器用 role="menu",每个选项用 role="menuitem",并确保它们都可聚焦(tabindex="0" 或原生可聚焦元素)。
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
用 focus-within 做键盘展开比 hover 更可靠
仅靠 group:hover .dropdown-menu 实现悬停展开,对键盘用户完全无效。必须支持 Enter 和 Space 展开,且用 focus-within 配合 JS 控制显隐更稳妥。
实操建议:
- 不要只依赖
hover显隐——它绕过可访问性要求 - 用
group-focus-within配合按钮的focus状态做视觉反馈(比如边框高亮) - 真正切换显示状态仍需 JS 控制
hidden类或内联display,因为 Tailwind 的group-hover无法响应键盘事件 - 按
Escape时必须关闭菜单并还原焦点
示例类名组合:group focus-within:ring-2 focus-within:ring-blue-500 加在触发按钮上,让键盘聚焦时有明确视觉提示。
移动端触摸与键盘操作必须共存
在 iOS 或安卓上,点击触发按钮后,若菜单出现但焦点没落到第一项,用户就得再点一次才能开始导航——这违背“一次操作进入可操作区域”的 WCAG 原则。
关键点:
- 触摸事件(
click)和键盘事件(keydown)都要监听,且逻辑一致 - 移动端 Safari 对
focus()有延迟或静默失败,建议加setTimeout微任务兜底:setTimeout(() => item.focus(), 0) - 菜单关闭时,避免用
opacity-0+pointer-events-none同时移除,否则屏幕阅读器可能读到“半透明不可点”的残留内容;优先用hidden或display: none
复杂点在于:同一个组件既要响应鼠标悬停、又要响应键盘焦点、还要兼容触摸点击,而 Tailwind 的工具类本身不感知事件源。所有交互逻辑必须由 JS 补齐,样式只是辅助呈现。

















