Front-End Checklist 无障碍实践:为 tooltip 提供可访问名称(aria-tooltip-name 规则深度解析)
2026/9/18 6:17:53 网站建设 项目流程

Front-End Checklist 无障碍实践:为 tooltip 提供可访问名称(aria-tooltip-name 规则深度解析)

【免费下载链接】Front-End-Checklist🗂 The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist

本篇技术指南基于 Front-End Checklist 开源仓库中的aria-tooltip-name规则文档,系统讲解为什么 tooltip(悬浮提示)必须具备可访问名称、如何通过aria-describedbyaria-label两种模式正确实现,并深入解读 WAI-ARIA 1.2 标准对齐、例外场景与自动化/手动验证流程。读完本文,你将能够审查任意前端项目中role="tooltip"元素的命名与关联正确性,并可在 React 组件中落地完整的可访问 tooltip 实现。

规则概览:这条规则在检查什么

在 Front-End Checklist 仓库中,aria-tooltip-name是一条位于accessibility/aria子类别下的规则,其核心检查目标是:

Checks that tooltip elements have accessible names—— 检查 tooltip 元素是否具有可访问名称。

规则元数据定义如下(优先级/难度/耗时均来自 源规则 MDX 文件 的 frontmatter):

元数据
Prioritymedium
Difficultyintermediate
Estimated Time10 min
Categoriesaccessibility(subcategory: aria)
检查方式验证所有role="tooltip"元素拥有可访问名称,或通过aria-describedby与触发元素正确关联

该规则在仓库中的存在形态有两处,内容同源:

  • Agent Skill 形态:skills/aria-tooltip-name/SKILL.md 与完整规则正文 skills/aria-tooltip-name/references/rule.md;
  • 规则源文件形态:packages/content/rules/en/accessibility/aria-tooltip-name.mdx,这也是 Skill 的生成母版。

两者之所以同源,是因为 Skill 目录由仓库的生成脚本 scripts/generate/generate-skills.ts 从规则 MDX 的 frontmatter 与正文自动产出:frontmatter 中的descriptiontldrprompts.checkprompts.fixprompts.explainprompts.codeReview字段被组装进SKILL.md,而 MDX 正文经stripMdxToMarkdown()转换后写入references/rule.md。理解了这条生成链路,你就能明白:本文讲解的内容同时适用于"人读的规则文档"与"Agent 读的 Skill 指令"两种消费方式

规则的一句话检查要点(来自源文件prompts.check):

验证所有role="tooltip"元素具有可访问名称,或通过aria-describedby关联到其触发元素。

修复要点(prompts.fix):

为 tooltip 分配可访问名称,或确保其被所描述的元素正确引用。

为什么 tooltip 必须拥有可访问名称

Tooltip 通常承载着按钮、图标或字段的补充说明信息——例如"保存"按钮旁边悬停出现的"将当前更改保存到云端"。如果这些信息没有被正确命名或关联到触发元素,那么屏幕阅读器用户将完全感知不到它们的存在。原规则文档从四个维度阐述了其重要性:

  • 信息平等(Information Equality):确保隐藏在 tooltip 中的文本对不使用鼠标的用户同样可用。tooltip 的触发方式以悬停为主,而鼠标悬停是纯指针交互,键盘用户与屏幕阅读器用户无法触发,因此必须在语义层面把信息"暴露"出来。
  • 触发元素感知(Trigger Awareness):帮助用户理解某个元素为何重要、如何使用。例如一个图标按钮只有悬浮提示才能传达其功能,若 tooltip 不可访问,该按钮对无障碍用户就只是一个无名的装饰。
  • WCAG 合规(WCAG Compliance):满足"悬停或聚焦时内容可见"相关的成功标准(如 WCAG 2.1 的 1.4.13 Content on Hover or Focus)。tooltip 若只在 hover 时出现,本身就违反键盘可达性要求。
  • 上下文帮助(Contextual Help):为无视觉用户提供与视力正常用户相同的"提示"信息,保证两类用户获得对等的操作指引。

在仓库的配套规则 accessible-tooltips(创建可访问的 tooltip) 中,这一点被进一步强化:

不可访问的 tooltip 会让键盘与屏幕阅读器用户失去重要的上下文信息,造成不平等的体验与潜在困惑。

同时该规则明确了一条边界:绝不把关键信息放进 tooltip。tooltip 只用于补充性提示(supplementary hints),关键内容必须默认可见或存在于主 UI 中——这一点在验证章节还会再次强调。

正确的实现模式:两种被认可的写法

原文档给出了两条正确示例,它们恰好覆盖了 tooltip 可访问名称的两种合法来源:由触发元素反向关联自带可访问名称

模式一:触发元素通过aria-describedby引用 tooltip

<!-- ✅ Correct: Trigger references tooltip via aria-describedby --> <button aria-describedby="tooltip-save"> Save </button> <div id="tooltip-save" role="tooltip"> Saves your current changes to the cloud </div>

这是 tooltip 最常见的实现形态。关键在于三点:

  1. role="tooltip"声明了该容器的语义角色;
  2. 触发元素(这里是<button>)通过aria-describedby="tooltip-save"与 tooltip 的id建立显式关联;
  3. 触发元素与 tooltip 的id一一对应,且页面内唯一。

当按钮获得焦点时,屏幕阅读器会同时朗读按钮的可访问名称("Save")和由aria-describedby引入的描述内容("Saves your current changes to the cloud"),悬浮提示的信息由此对无视觉用户生效。这正是prompts.check中所说的"通过aria-describedby链接到触发元素"。

模式二:tooltip 自带可访问名称(aria-label

<!-- ✅ Correct: Tooltip with its own name --> <div role="tooltip" aria-label="Keyboard shortcuts info"> Press Ctrl+S to save </div>

当 tooltip 内容本身需要先被"命名"再被阅读时,可以直接在 tooltip 元素上通过aria-label提供可访问名称。需要理解的是,此时屏幕阅读器朗读的是aria-label的值("Keyboard shortcuts info"),而 tooltip 的可见文本内容("Press Ctrl+S to save")作为补充信息仍然存在于 DOM 中,可被点读或进一步探索。

可访问名称的计算顺序

结合 WAI-ARIA 1.2 规范(这也是本规则的标准来源之一),元素的可访问名称(accessible name)按以下优先级计算,这有助于你判断"tooltip 是否有名字":

  1. aria-labelledby引用的元素内容(显式命名);
  2. aria-label属性值(显式命名);
  3. 元素自身内容(如可见文本,仅对特定角色生效);
  4. title属性兜底。

对于role="tooltip"元素,规则要求的"有名字"即对应上述前两条路径。这也解释了为什么aria-describedby关联(模式一)与aria-label自命名(模式二)是文档认可的两类修复方式。

完整落地:从规则到可运行的 React Tooltip 组件

规则本身给出的是最小语义骨架,而仓库中的配套规则 accessible-tooltips 提供了从骨架到生产组件的完整实现。二者的关系可以这样理解:aria-tooltip-name解决"tooltip 有没有名字"的验收问题,accessible-tooltips解决"tooltip 组件如何写才可访问"的实现问题。

可访问 tooltip 的五项硬性要求

配套规则以表格形式列出了可访问 tooltip 必须满足的需求与实现方式:

需求实现
键盘可达(Keyboard accessible)在 focus 时显示,而非仅 hover
程序化关联(Programmatically associated)使用aria-describedby
可关闭(Dismissible)按 Escape 键关闭
持续可见(Persistent)悬停/聚焦期间保持显示
非关键信息(Non-essential)不要把关键信息藏进 tooltip

注意第一行:"show on focus, not just hover"——这正是 WCAG "Content on Hover or Focus" 精神在组件层的落实,也是aria-tooltip-name规则在键盘验证环节的前提。

React 基础组件实现

配套规则给出的 React 实现完整演示了如何用useId()生成稳定的aria-describedby关联:

import { useState, useRef, useEffect, useId } from 'react' interface TooltipProps { content: string children: React.ReactElement position?: 'top' | 'bottom' | 'left' | 'right' delay?: number } export function Tooltip({ content, children, position = 'top', delay = 300 }: TooltipProps) { const [isVisible, setIsVisible] = useState(false) const tooltipId = useId() const timeoutRef = useRef<NodeJS.Timeout>() const triggerRef = useRef<HTMLElement>(null) const showTooltip = () => { timeoutRef.current = setTimeout(() => setIsVisible(true), delay) } const hideTooltip = () => { clearTimeout(timeoutRef.current) setIsVisible(false) } // Handle Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && isVisible) { hideTooltip() } } document.addEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown) }, [isVisible]) // Clone child to add props const trigger = React.cloneElement(children, { ref: triggerRef, 'aria-describedby': isVisible ? tooltipId : undefined, onMouseEnter: showTooltip, onMouseLeave: hideTooltip, onFocus: showTooltip, onBlur: hideTooltip, }) return ( <div className="tooltip-wrapper"> {trigger} {isVisible && ( <div id={tooltipId} role="tooltip" className={`tooltip tooltip--${position}`} > {content} </div> )} </div> ) }

这段代码与aria-tooltip-name规则严格对齐的关键细节:

  • useId()生成tooltipId,同时注入到触发元素的aria-describedby与 tooltip 的id确保两者关联在 SSR/CSR 下都不会重复或错位
  • onFocus: showTooltiponBlur: hideTooltip实现了"focus 时显示",让键盘用户与鼠标用户获得同等信息——这正是规则"Trigger Awareness"要点的实现;
  • Escape 关闭逻辑对应配套规则"可关闭(Dismissible)"需求;
  • delay默认 300ms 的防误触延迟是常见 UX 实践,可配置。

使用方式如下:

<Tooltip content="Save your current progress"> <button type="button"> <SaveIcon aria-hidden="true" /> <span className="sr-only">Save</span> </button> </Tooltip> <Tooltip content="Required field" position="right"> <label htmlFor="email"> Email <span aria-hidden="true">*</span> </label> </Tooltip>

注意第一个用例:图标按钮的可访问名称来自<span className="sr-only">Save</span>(屏幕阅读器专用文本),而 tooltip 通过aria-describedby补充"Save your current progress"——按钮有名字、tooltip 有关联,二者各司其职。

使用 Floating UI 的高级实现

对于需要自动避让视口边缘的浮层定位场景,配套规则还提供了基于@floating-ui/react的实现,核心差异在于用useFloatingrefs.setReference/refs.setFloating接管定位,并组合offsetflipshiftarrow中间件:

import { useFloating, offset, flip, shift, arrow } from '@floating-ui/react' import { useState, useRef, useId } from 'react' interface AdvancedTooltipProps { content: React.ReactNode children: React.ReactElement } export function AdvancedTooltip({ content, children }: AdvancedTooltipProps) { const [isOpen, setIsOpen] = useState(false) const arrowRef = useRef(null) const tooltipId = useId() const { refs, floatingStyles, context } = useFloating({ open: isOpen, onOpenChange: setIsOpen, placement: 'top', middleware: [ offset(8), flip(), shift({ padding: 8 }), arrow({ element: arrowRef }) ], }) return ( <> {React.cloneElement(children, { ref: refs.setReference, 'aria-describedby': isOpen ? tooltipId : undefined, onMouseEnter: () => setIsOpen(true), onMouseLeave: () => setIsOpen(false), onFocus: () => setIsOpen(true), onBlur: () => setIsOpen(false), })} {isOpen && ( <div ref={refs.setFloating} id={tooltipId} role="tooltip" style={floatingStyles} className="tooltip" > {content} <div ref={arrowRef} className="tooltip-arrow" /> </div> )} </> ) }

无论定位策略如何演进,无障碍契约保持不变:role="tooltip"aria-describedby关联、focus/blur 触发、Escape 关闭。这印证了本规则"以渲染结果为准,而非仅看源码抽象"的审查立场——组件的定位复杂度不应破坏语义关联。

图标按钮 + tooltip 的组合模式

图标按钮是 tooltip 最常见的应用场景,配套规则给出了一种先保底再增强的写法:按钮始终用aria-label提供自己的可访问名称,tooltip 仅在可见时才通过aria-describedby挂载,避免隐藏状态下产生多余的描述关联:

export function IconButton({ icon, label, onClick, tooltip }: IconButtonProps) { const tooltipId = useId() const [showTooltip, setShowTooltip] = useState(false) return ( <div className="icon-button-wrapper"> <button type="button" onClick={onClick} aria-label={label} aria-describedby={tooltip && showTooltip ? tooltipId : undefined} onMouseEnter={() => setShowTooltip(true)} onMouseLeave={() => setShowTooltip(false)} onFocus={() => setShowTooltip(true)} onBlur={() => setShowTooltip(false)} className="icon-button" > {icon} </button> {tooltip && showTooltip && ( <span id={tooltipId} role="tooltip" className="tooltip"> {tooltip} </span> )} </div> ) }

这条模式对本规则有直接意义:tooltip 与触发元素的关联不必始终存在,但必须保证在"信息需要被传达的时刻"(可见/聚焦时)存在且有效。审查时若发现条件渲染导致aria-describedby时有时无,需要确认"有"的分支语义完整,这正是手动验证阶段要覆盖的交互状态。

原生title属性的局限

配套规则还对比了原生title属性与自定义 tooltip:

<!-- Simple but limited accessibility --> <button type="button" title="Save document"> <svg aria-hidden="true"><!-- save icon --></svg> <span class="sr-only">Save</span> </button> <!-- Better: Custom tooltip with full control --> <button type="button" aria-describedby="save-tooltip" aria-label="Save" > <svg aria-hidden="true"><!-- save icon --></svg> </button> <div id="save-tooltip" role="tooltip" class="tooltip"> Save document (Ctrl+S) </div>

title的问题在于:其显示依赖鼠标悬停、延迟不可控、且不能保证被所有屏幕阅读器朗读。因此当需要完整的可访问 tooltip 时,应使用role="tooltip"+aria-describedby的自定义实现,并辅以 CSS 控制显示。配套规则还给出了配套的 CSS(含prefers-reduced-motion降级与对比度要求,#ffffffon#1a1a1a约为 16.1:1,满足 WCAG 4.5:1 正常文本对比度),可从 accessible-tooltips 的 Styling 一节直接取用。

例外情况:什么时候不该机械套用本规则

原文档明确列出了三条例外,用于防止过度修复:

  1. 优先原生 HTML 语义:当原生元素能做到时,优先使用原生语义而非 ARIA。许多"看似 ARIA 缺失"的问题,在底层元素被修正为正确语义后自然消失。例如一个缺失名称的<button>,补上可见文本比硬塞aria-label更符合本意。
  2. 缺失 ARIA 不一定是首要问题:如果控件本身已语义破损、无名或键盘不可达,缺失 ARIA 属性不是最强的发现项。审查时应先解决更根本的语义问题——这与 Skill 中aiContext提示的"先检查原生语义,再检查键盘行为、焦点流、可访问名称"的审查顺序一致。
  3. 不要为满足规则而硬加 ARIA:如果该功能本应使用原生元素或更简单的交互模式实现,就不应为了通过检查而堆砌 ARIA。这呼应了配套规则的忠告:tooltip 只放补充信息,关键内容应默认可见。

标准对齐:WAI-ARIA 1.2 与 MDN

原文档的 Standards 一节给出两条对齐要求(对应源文件 frontmatter 中的sources,标记为 primary 权威来源):

  • WAI-ARIA 1.2wai-aria-1-2,标准来源,primary):role="tooltip"角色的定义、可访问名称计算、与aria-describedby的配合均以该规范为准;
  • MDN: ARIAmdn-aria,参考来源,primary):用于核对各角色的浏览器支持情况与可访问名称计算的实现细节。

同时文档强调一条核心原则:对齐标准的同时必须验证渲染后的实际体验,而不是只看源码("verify the rendered experience, not only the source code")。这与仓库中 Agent Skill 的codeReview提示完全一致——审查应针对渲染后的标记与交互状态,而非框架层的 JSX 抽象。例如 React 组件里写了aria-describedby,若最终 DOM 因条件渲染而未输出该属性,源码审查就会漏判。

验证方法:自动化与手动双重确认

原文档提供了两层验证方案,可用于代码审查、QA 测试或 Agent 审计场景。

自动化检查

  • 在浏览器的可访问性树(accessibility tree)或 accessibility pane中检查相关元素、角色与可访问名称:确认 tooltip 节点存在role="tooltip",并核对它及其触发元素的可访问名称计算是否符合预期;
  • 运行自动化可访问性检查器,如 axe 或 Lighthouse(源文件 frontmatter 的resources中也列出了 axe DevTools 作为推荐工具)。这类工具能直接抓取"有role="tooltip"但无可访问名称"的失败用例。

手动检查

  • 纯键盘导航测试:仅用键盘 Tab 到触发元素,确认 tooltip 在 focus 时出现、按 Escape 可关闭、焦点移开后消失,并在真实渲染环境中确认规则成立;
  • 屏幕阅读器复测:若本规则影响关键交互,至少用一款屏幕阅读器重测一个代表性用户流程,确认 tooltip 的补充信息确实被朗读出来(触发元素名称 +aria-describedby引入的描述文本)。

配套规则 accessible-tooltips 还提供了一份更细的七步验证清单,可直接作为回归用例:

  1. Tab 到触发元素——tooltip 应出现;
  2. 按 Escape——tooltip 应关闭;
  3. 悬停触发元素——延迟后 tooltip 出现;
  4. 鼠标移动到 tooltip 上——应保持可见;
  5. 用屏幕阅读器测试——tooltip 内容应被朗读;
  6. 确认 tooltip 不遮挡其他内容;
  7. 检查颜色对比度满足 WCAG 要求。

相关规则与审查顺序

源文件 frontmatter 通过relatedRules将本规则与同一accessibility/aria区域的命名类规则关联,它们"通常一起被审查":

  • aria-treeitem-name:树项的可访问名称;
  • aria-command-name:命令类控件(button/link/menuitem 等)的可访问名称;
  • aria-dialog-name:对话框的可访问名称;
  • aria-input-field-name:输入框的可访问名称。

这些规则共同构成"ARIA 命名完整性"审查族:审查一个交互组件时,同一容器内的按钮、输入框、对话框、tooltip 都可能各自触发其中一条。此外,从组件实现维度看,还应与本规则的实现侧伴侣 accessible-tooltips(创建可访问的 tooltip) 配套使用——前者验收"命名",后者指导"实现"。

在 Front-End Checklist 项目中的定位与使用方式

本规则是 Front-End Checklist 规则库(packages/content/rules/en/下按类别组织的 385+ 条 MDX 规则之一)中accessibility类别的一员,同时收录于规则目录 docs/generated/rules-catalog.md(其中第 251 行即本规则条目,标记为 Medium 优先级,检查项描述为"Checks that tooltip elements have accessible names")。

对于开发者与 AI Agent,仓库提供了两种消费方式:

  • 作为 Agent Skill 安装(见 README.md 的 "Use with skills" 一节):整体安装全部技能,或按需安装单个规则技能:
    npx skills add frontendchecklist/skills npx skills add frontendchecklist/skills --skill aria-tooltip-name

    安装后,Agent 在审查渲染后的 HTML、交互组件或设计系统模式时,会遵循 Skill 中aiContext给定的顺序:先检查原生语义,再检查键盘行为、焦点流、可访问名称与屏幕阅读器输出。

  • 作为规则文档阅读:直接阅读 skills/aria-tooltip-name/references/rule.md(纯 Markdown 版)或源文件 packages/content/rules/en/accessibility/aria-tooltip-name.mdx(含 frontmatter 中的promptssources元数据)。

小结

aria-tooltip-name规则看似只关心一行 ARIA 属性,实则贯穿了可访问 tooltip 的完整生命周期:命名aria-labelaria-describedby关联)、键盘可达(focus 触发 + Escape 关闭)、语义正确(优先原生元素)、标准对齐(WAI-ARIA 1.2 与 MDN)、双重验证(自动化 + 手动)。将其与仓库中的配套实现规则配合使用,你既能写出通过检查的组件,也能在审查他人代码时给出有理有据的修复建议。

核心操作清单(可直接用于你的下一次代码审查):

  1. 搜索所有role="tooltip"元素,逐一确认其拥有可访问名称(aria-label)或被触发元素通过aria-describedby引用;
  2. 检查触发元素是否在onFocus时显示 tooltip、onBlur/Escape 时关闭;
  3. 打开浏览器可访问性树核对渲染结果,再用键盘 + 屏幕阅读器做一轮端到端验证;
  4. 遇到"缺 ARIA"的发现时,先判断底层控件是否语义破损、是否能用原生元素替代,再决定修复方式。

【免费下载链接】Front-End-Checklist🗂 The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询