跳到正文
册页

浮层

Tooltip - 文字提示

悬停和获得焦点时出现的一句短标签。

用法

什么时候用它

任何读者“需要”的东西都不能只放在这里:触屏摸不到它,扫读的人也看不见它。
TSX
import { Tooltip } from '@misoto22/folio'

说明

悬停和聚焦时出现的一句短标签。

触发器上的 asChild 是设计使然:tooltip 不能加一层会吞掉触发器自己的焦点环、或者打断 flex 行的包装。这也意味着子元素必须可聚焦——一个 <div> 触发器拿不到键盘 tooltip,而这个 API 形状让这个故障显而易见,而不是悄无声息。

它不能代替可访问名称。纯图标按钮仍然需要自己的 aria-label;tooltip 只是把那个名字,为用鼠标的视力正常用户再说一遍。

结构

Tooltip anatomy
部件说明
提供者必填TooltipProvider,在应用外面、或者在有 tooltip 的那棵最小子树外面包一次。共享的 700ms 打开延迟和 300ms 跳过窗口都在它身上;没有它,Radix 是直接抛错,而不是渲染一个没有节奏的提示。
触发器必填children,以 asChild 交给 Radix——所以子元素本身就是触发器,外面不会插进任何包装元素。
提示必填content,装在 portal 出去的那个面板里:--feature-surface 底上的 11px 等宽字,宽度上限 16rem,离所选那一侧 6px,停在 --z-toast(300),所以它在自己被打开的那个模态之上仍然看得见。
portal 落点提示最终落在哪——document.body,或者外层 OverlayContainer 指名的那个元素;它同时也是提示以 8px 内边距去碰撞的那个盒子。

实践建议

推荐

  • Provider 要放得高,而且必须放一个。没有它 Radix 会抛错;而每个 tooltip 各配一个 provider,就废掉了那份共享节奏——正是它挡住了一排图标按钮在每次悬停时各闪各的提示。
  • 提示要和控件的 aria-label 逐字一致:一个控件两个名字,就是「名称中的标签」这条失败(WCAG 2.5.3),而语音控制用户念出来的,是他看得见的那几个字。
  • 提示只写一个短语。它的宽度上限是 16rem,字号是 11px 等宽,所以一整句话会折成五行的一块,把它本来要描述的那个东西盖住。
  • 把它放在一个没有它也照样能用的控件上:指针类型是触摸时 Radix 直接提前返回,所以在手机上这个提示压根不会打开,凡是只由它承载的东西,在那里就是缺失的。

避免

  • delayDuration={0} 不只是让它更快——状态会变成 instant-open 而不是 delayed-open,而淡入是绑在 delayed-open 上的,所以提示是完全没有过渡地直接出现。
  • 在单个 Tooltip 上设 delayDuration,只为那一个触发器覆盖 provider——于是一条工具栏里有一个瞬间弹出的提示,紧挨着一排 700ms 的邻居,读起来是卡顿,不是强调。
  • content 里不该有任何可聚焦的东西:提示不在 Tab 顺序里,触发器一失焦它就关,所以放在里面的链接或按钮,除了指针没有第二种够得到的方式。那是 Popover 的活。

示例

an icon button

一个提示,挂在只带图形、不带文字的控件上。提示文字、图标和按钮自己的无障碍名称是一起变的,因为一个前后长得一模一样的复制控件,会让读者为了确认再点一次。让提示和 aria-label 逐字对上:同一个控件有两个不同的名字,就是「名称中的标签」失败项(WCAG 2.5.3),而一个用语音控制的用户念的是他看得见的那些词。提示也不是名称——Radix 遇到触摸指针会提前返回,所以在手机上它根本不会打开,而一个没有自己 aria-label 的图标按钮就是没有名字的。

one provider

一个 Provider 罩住整条工具栏,这正是它该在的地方:它握着共享的 700ms 打开延迟和 300ms 跳过窗口,于是沿着这一行移动会立刻打开下一个提示,而不是在每个按钮上闪一个各自为政的。没有 Provider,Radix 会抛错,而给每个 tooltip 各配一个 Provider,恰好把那份共享的时序丢掉了。别为了让某一个快一点,就在单个 Tooltip 上设 delayDuration——那只会为那一个触发器覆盖 provider,而一个瞬间弹出的提示,夹在一堆 700ms 的邻居中间,读起来像卡顿,不像强调。设成零更糟:状态会变成 instant-open,而淡入是挂在 delayed-open 上的,于是提示出现时完全没有过渡。

a shortcut hint

控件在页面上说明它做什么;提示只补上那个快捷键。tooltip 在触摸设备上够不着,对任何扫读的人不可见,焦点一离开就消失——所以任何读者需要的东西,都不能只活在一个 tooltip 里。还有两条限制值得知道:提示以 11px 等宽字排、宽度上限 16rem,所以一句话会折成一个五行的块,盖住它所描述的那个东西;另外,任何可聚焦的东西都不该放进 content,因为提示不在 tab 顺序里,而且触发器一失焦它就关。放在里面的链接或按钮,只有指针够得着,别的什么都够不着。那种是 Popover。

The same keys are printed on the page, so S is not news a hover has to break.

再导出

TooltipProvider = TooltipPrimitive.Provider

在应用外面——或者包住所有 tooltip 的那棵最小子树——包一次。Radix 需要它来在相邻触发器之间共享开合的时机,正是这一点让一排图标按钮不会每悬停一次就闪一个 tooltip。

键盘操作

Tooltip keyboard interactions
按键作用
Tab显示提示——聚焦就会出现,不是只有悬停才行。
Escape关掉提示。

无障碍

  • 在有边界的框里——设备预览、内嵌控制台——用 `<OverlayContainer container={el}>` 包住这棵子树。面板会渲染进那个元素,按它的边界翻转,而不是按视口;框上设的 `dir` 和 `data-density` 也就跟着生效了。
  • 触发器用 asChild,所以子元素必须可聚焦——一个 div 触发器就是没有键盘 tooltip,这个 API 形状让它显性而不是无声。
  • 它不是无障碍名称。只有图标的按钮仍然需要自己的 aria-label。