创建纯 CSS 工具提示
如果你需要一个轻量级的工具提示(tooltip),又不想为此引入一个 JavaScript 库,那么仅用 CSS 就能出色地完成这项任务。无需依赖、无需事件监听器——只需几条精心编写的 CSS 规则即可。
本文将带你了解一种实用且现代的实现方式,使用伪元素、data 属性和过渡效果,并介绍你确实需要了解的可访问性注意事项。
关键要点
- 纯 CSS 工具提示可以通过
::after伪元素、content: attr(data-tooltip)以及:hover/:focus-visible触发器来构建。 - 使用
opacity和visibility(而非display),以便工具提示能够平滑淡入淡出。 pointer-events: none可防止工具提示自身接收悬停事件,从而避免闪烁。- CSS 工具提示存在真实的可访问性限制——它们无法可靠地被屏幕阅读器识别,并且在触屏设备上的悬停行为也不一致。
- 对于关键 UI 提示,请使用 JavaScript、Popover API,或通过
aria-describedby正确引用可见元素。
纯 CSS 工具提示的工作原理
核心思路很简单:在触发元素上应用 position: relative,然后将 ::after 伪元素作为工具提示气泡,相对其父元素进行绝对定位。默认情况下隐藏它,并在 :hover 和 :focus-visible 时显示。
由于伪元素无法直接读取 DOM,因此你需要通过 data-tooltip 属性传递工具提示文本,并使用 content: attr(data-tooltip) 将其引入。这样可以保持 HTML 整洁,并避免在隐藏的 <span> 中重复书写文本。
HTML 结构
<button
class="tooltip-trigger"
data-tooltip="Saves your current progress"
>
Save
</button>
使用 <button> 或锚点(<a>)作为触发元素非常重要。这些元素天生可获得焦点,这意味着键盘用户无需任何额外操作即可访问到它们。
注意:如果你想使用
aria-describedby,它必须指向 DOM 中真实存在且可见元素的id——而非伪元素。由于本技术使用::after,除非你也将工具提示文本渲染在真实元素中,否则请省略aria-describedby。
CSS 实现
.tooltip-trigger {
position: relative;
cursor: pointer;
}
/* Tooltip bubble */
.tooltip-trigger::after {
content: attr(data-tooltip);
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background-color: #1a1a1a;
color: #fff;
font-size: 0.8rem;
white-space: nowrap;
padding: 5px 10px;
border-radius: 4px;
/* Hidden by default */
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease, visibility 0.2s ease;
z-index: 10;
pointer-events: none;
}
/* Show on hover and keyboard focus */
.tooltip-trigger:hover::after,
.tooltip-trigger:focus-visible::after {
opacity: 1;
visibility: visible;
}
为什么使用 opacity + visibility 而不是 display?
display: none 和 display: block 之间的切换无法应用过渡效果——工具提示只会突然出现和消失。同时使用 opacity 与 visibility: hidden 可以让工具提示平滑淡入淡出,并在隐藏时阻止其响应指针交互。配合 visibility 与 opacity 还可以防止隐藏的工具提示在视觉上仍可交互。
为什么使用 pointer-events: none?
如果没有这一属性,工具提示气泡本身可能会触发悬停状态,导致光标移动到其上时工具提示出现闪烁。在 ::after 元素上设置 pointer-events: none 即可完全避免该问题。
Discover how at OpenReplay.com.
为什么使用 transform: translateX(-50%) 进行居中?
设置 left: 50% 会将工具提示的左边缘移到触发元素的中心。然后 translateX(-50%) 将其向回偏移自身宽度的一半,无论工具提示文本多宽都能实现居中——无需任何硬编码的像素计算。
需要了解的可访问性限制
纯 CSS 悬停工具提示存在以下实际限制:
- 屏幕阅读器可能不会朗读它。 伪元素上的
content属性并非所有屏幕阅读器都能可靠读取。对于关键信息,应将其包含在可见 UI 中,或使用aria-describedby指向真实元素。 - 触屏设备上的悬停行为不一致。 不应依赖纯 CSS 工具提示来传达必要信息。
- 无角色或活动区域(live region)。 仅靠 CSS 无法向辅助技术传达工具提示的语义。
对于交互元素上简单的补充提示,CSS 工具提示足够使用。但对于完成任务所必需的内容,则需要使用 JavaScript 来管理焦点、播报和 ARIA 状态。
展望:CSS 锚点定位与 Popover API
CSS 锚点定位(Anchor Positioning) 让工具提示的位置控制更加灵活——你可以将工具提示锚定到任何元素,而无需依赖父元素的 position: relative。根据 Can I Use 的数据,锚点定位现已在主流现代浏览器中得到广泛支持。
如需更丰富的交互行为,Popover API 是一个值得探索的原生替代方案,适用于交互式浮动 UI。
结论
使用 ::after、content: attr(data-tooltip) 以及 visibility/opacity 过渡构建的纯 CSS 工具提示既简洁又快速,且无任何依赖。再配合 :focus-visible 实现键盘支持,便能形成稳固的基础方案。但请务必认清 CSS 工具提示的局限性——除补充提示外,任何更复杂的场景都应使用 JavaScript 或 Popover API 等原生平台特性。
常见问题
可以。位置由伪元素上的偏移属性控制。使用 bottom 配合 calc(100% + 8px) 可放置在上方,使用 top 配合 calc(100% + 8px) 可放置在下方,或使用 right 和 left 进行水平方向放置。你也可以创建诸如 tooltip-bottom 或 tooltip-right 这样的变体类来覆盖这些属性。
触屏设备上的悬停行为不一致,因此不应依赖纯 CSS 工具提示传达必要信息。如果工具提示内容在移动端很重要,请将其渲染在由 JavaScript 控制的真实元素中,或使用 Popover API,它为跨输入类型的交互式浮动 UI 提供了更可靠的基础。
white-space: nowrap 规则让工具提示保持单行显示,但在窄屏上可能溢出。可以将其替换为 max-width 并允许换行,例如 max-width: 240px 和 white-space: normal。对于动态边缘检测,则需要 JavaScript 或 CSS 锚点定位,后者在现代浏览器中支持更灵活的原生定位行为。
对于按钮和链接等可获得焦点元素上的非必需补充提示,它是可接受的。但当工具提示承载完成任务所需的信息时,它就不够用了,因为伪元素内容无法被屏幕阅读器可靠地播报,且触屏设备上的悬停行为不一致。对于关键内容,应使用真实 DOM 元素配合 aria-describedby 和 JavaScript 管理的行为,或考虑使用 Popover API。
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..