backdrop
组件

Tooltip

当元素获得键盘焦点或鼠标悬停时显示与该元素相关信息的弹出框。

功能特性

  • 提供全局控制显示延迟的 Provider。
  • 在触发器获得焦点或悬停时打开。
  • 在触发器被激活或按下 Escape 键时关闭。
  • 支持自定义计时设置。

结构组成

导入所有部件并将其组合在一起。

vue
<script setup lang="ts">
import { TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
</script>

<template>
  <TooltipProvider>
    <TooltipRoot>
      <TooltipTrigger />
      <TooltipPortal>
        <TooltipContent>
          <TooltipArrow />
        </TooltipContent>
      </TooltipPortal>
    </TooltipRoot>
  </TooltipProvider>
</template>

API 参考

Provider

包装您的应用,为工具提示提供全局功能。

PropDefaultType
delayDuration
700
number

The duration from when the pointer enters the trigger until the tooltip gets opened.

disableClosingTrigger
boolean

When true, clicking on trigger will not close the content.

disabled
boolean

When true, disable tooltip

disableHoverableContent
false
boolean

When true, trying to hover the content will result in the tooltip closing as the pointer leaves the trigger.

ignoreNonKeyboardFocus
false
boolean

Prevent the tooltip from opening if the focus did not come from the keyboard by matching against the :focus-visible selector. This is useful if you want to avoid opening it when switching browser tabs or closing a dialog.

skipDelayDuration
300
number

How much time a user has to enter another trigger without incurring a delay again.

Root

包含工具提示的所有部分。

PropDefaultType
defaultOpen
false
boolean

The open state of the tooltip when it is initially rendered. Use when you do not need to control its open state.

delayDuration
number

Override the duration given to the Provider to customise the open delay for a specific tooltip.

disableClosingTrigger
boolean

When true, clicking on trigger will not close the content.

disabled
boolean

When true, disable tooltip

disableHoverableContent
boolean

Prevents Tooltip.Content from remaining open when hovering. Disabling this has accessibility consequences. Inherits from Tooltip.Provider.

ignoreNonKeyboardFocus
boolean

Prevent the tooltip from opening if the focus did not come from the keyboard by matching against the :focus-visible selector. This is useful if you want to avoid opening it when switching browser tabs or closing a dialog.

open
boolean

The controlled open state of the tooltip.

EmitPayload
update:open
[value: boolean]

Event handler called when the open state of the tooltip changes.

Slots (default)Payload
open
boolean

Current open state

Trigger

用于切换工具提示的按钮。默认情况下,TooltipContent 将相对于触发器进行定位。

PropDefaultType
as
'button'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

reference
ReferenceElement

The reference (or anchor) element that is being referred to for positioning.

If not provided will use the current component as anchor.

Data AttributeValue
[data-state]"closed" | "delayed-open" | "instant-open"

Portal

使用时,将内容部分传送到 body 中。

PropDefaultType
defer
boolean

Defer the resolving of a Teleport target until other parts of the application have mounted (requires Vue 3.5.0+)

reference

disabled
boolean

Disable teleport and render the component inline

reference

forceMount
boolean

Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.

to
string | HTMLElement

Vue native teleport component prop :to

reference

Content

工具提示打开时弹出的组件。

tip
Built with Presence component - supports any animation techniques while maintaining access to presence emitted events.
PropDefaultType
align
'start' | 'center' | 'end'

The preferred alignment against the trigger. May change when collisions occur.

alignOffset
number

An offset in pixels from the start or end alignment options.

ariaLabel
string

By default, screenreaders will announce the content inside the component. If this is not descriptive enough, or you have content that cannot be announced, use aria-label as a more descriptive label.

arrowPadding
number

The padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners.

as
'div'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

avoidCollisions
boolean

When true, overrides the side and align preferences to prevent collisions with boundary edges.

collisionBoundary
Element | (Element | null)[] | null

The element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check.

collisionPadding
number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>

The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { top: 20, left: 20 }.

forceMount
boolean

Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.

hideWhenDetached
boolean

Whether to hide the content when the trigger becomes fully occluded.

positionStrategy
'fixed' | 'absolute'

The type of CSS position property to use.

side
'top'
'top' | 'right' | 'bottom' | 'left'

The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled.

sideOffset
number

The distance in pixels from the trigger.

sticky
'partial' | 'always'

The sticky behavior on the align axis. partial will keep the content in the boundary as long as the trigger is at least partially in the boundary whilst "always" will keep the content in the boundary regardless.

updatePositionStrategy
'always' | 'optimized'

Strategy to update the position of the floating element on every animation frame.

EmitPayload
escapeKeyDown
[event: KeyboardEvent]

Event handler called when focus moves to the destructive action after opening. It can be prevented by calling event.preventDefault

pointerDownOutside
[event: Event]

Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling event.preventDefault.

Data AttributeValue
[data-state]"closed" | "delayed-open" | "instant-open"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
CSS VariableDescription
--reka-tooltip-content-transform-origin
根据内容和箭头的位置/偏移计算出的 transform-origin
--reka-tooltip-content-available-width
触发器与边界边缘之间的剩余宽度
--reka-tooltip-content-available-height
触发器与边界边缘之间的剩余高度
--reka-tooltip-trigger-width
触发器的宽度
--reka-tooltip-trigger-height
触发器的高度

Arrow

一个可选的箭头元素,与工具提示一同渲染。可用于在视觉上连接触发器与 TooltipContent。必须在 TooltipContent 内部渲染。

PropDefaultType
as
'svg'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

height
5
number

The height of the arrow in pixels.

width
10
number

The width of the arrow in pixels.

示例

全局配置

使用 Provider 全局控制 delayDurationskipDelayDuration

vue
<script setup>
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
</script>

<template>
  <TooltipProvider
    :delay-duration="800"
    :skip-delay-duration="500"
  >
    <TooltipRoot>
      <TooltipTrigger>…</TooltipTrigger>
      <TooltipContent>…</TooltipContent>
    </TooltipRoot>
    <TooltipRoot>
      <TooltipTrigger>…</TooltipTrigger>
      <TooltipContent>…</TooltipContent>
    </TooltipRoot>
  </TooltipProvider>
</template>

立即显示

使用 delayDuration 属性控制工具提示打开所需的时间。

vue
<script setup>
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
</script>

<template>
  <TooltipRoot :delay-duration="0">
    <TooltipTrigger>…</TooltipTrigger>
    <TooltipContent>…</TooltipContent>
  </TooltipRoot>
</template>

从禁用按钮显示工具提示

由于禁用按钮不会触发事件,您需要:

  • Trigger 渲染为 span
  • 确保 button 没有 pointerEvents
vue
<script setup>
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
</script>

<template>
  <TooltipRoot>
    <TooltipTrigger as-child>
      <span tabindex="0">
        <button
          disabled
          style="{ pointerEvents: 'none' }"
        >…</button>
      </span>
    </TooltipTrigger>
    <TooltipContent>…</TooltipContent>
  </TooltipRoot>
</template>

限制内容尺寸

您可能希望限制内容的宽度以匹配触发器的宽度。您还可能希望限制其高度以不超出视口。

我们暴露了多个 CSS 自定义属性,如 --reka-tooltip-trigger-width--reka-tooltip-content-available-height 来支持此功能。使用它们来约束内容尺寸。

vue
<script setup>
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
</script>

<template>
  <TooltipRoot>
    <TooltipTrigger>…</TooltipTrigger>
    <TooltipPortal>
      <TooltipContent
        class="TooltipContent"
        :side-offset="5"
      >

      </TooltipContent>
    </TooltipPortal>
  </TooltipRoot>
</template>
css
/* styles.css */
.TooltipContent {
  width: var(--reka-tooltip-trigger-width);
  max-height: var(--reka-tooltip-content-available-height);
}

感知来源的动画

我们暴露了一个 CSS 自定义属性 --reka-tooltip-content-transform-origin。使用它可以根据 sidesideOffsetalignalignOffset 和任何碰撞,从计算出的原点对内容进行动画处理。

vue
<script setup>
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
</script>

<template>
  <TooltipRoot>
    <TooltipTrigger>…</TooltipTrigger>
    <TooltipContent class="TooltipContent">

    </TooltipContent>
  </TooltipRoot>
</template>
css
/* styles.css */
.TooltipContent {
  transform-origin: var(--reka-tooltip-content-transform-origin);
  animation: scaleIn 0.5s ease-out;
}

@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

感知碰撞的动画

我们暴露了 data-sidedata-align 属性。它们的值将在运行时改变以反映碰撞。使用它们来创建碰撞和方向感知的动画。

vue
<script setup>
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
</script>

<template>
  <TooltipRoot>
    <TooltipTrigger>…</TooltipTrigger>
    <TooltipContent class="TooltipContent">

    </TooltipContent>
  </TooltipRoot>
</template>
css
/* styles.css */
.TooltipContent {
  animation-duration: 0.6s;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.TooltipContent[data-side="top"] {
  animation-name: slideUp;
}
.TooltipContent[data-side="bottom"] {
  animation-name: slideDown;
}

@keyframes slideDown {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

可访问性

键盘交互

KeyDescription
Tab
无延迟地打开/关闭工具提示。
Space
如果已打开,则无延迟地关闭工具提示。
Enter
如果已打开,则无延迟地关闭工具提示。
Escape
如果已打开,则无延迟地关闭工具提示。

自定义 API

通过将原始部件抽象到您自己的组件中来创建您自己的 API。

抽象部件并引入内容属性

此示例抽象了所有 Tooltip 部件,并引入了一个新的 content 属性。

用法

vue
<script setup lang="ts">
import { Tooltip } from './your-tooltip'
</script>

<template>
  <Tooltip content="工具提示内容">
    <button>工具提示触发器</button>
  </Tooltip>
</template>

实现

使用 asChild 属性 将触发器部分转换为可插槽区域。它将用传递给它的子项替换触发器。

vue
<!-- your-tooltip.vue  -->
<script setup lang="ts">
import type { TooltipRootEmits, TooltipRootProps } from 'reka-ui'
import { TooltipArrow, TooltipContent, TooltipRoot, TooltipTrigger, useForwardPropsEmits } from 'reka-ui'

const props = defineProps<TooltipRootProps & { content?: string }>()
const emits = defineEmits<TooltipRootEmits>()

const forward = useForwardPropsEmits(props, emits)
</script>

<template>
  <TooltipRoot v-bind="forward">
    <TooltipTrigger as-child>
      <slot />
    </TooltipTrigger>
    <TooltipContent
      side="top"
      align="center"
    >
      {{ content }}
      <TooltipArrow
        :width="11"
        :height="5"
      />
    </TooltipContent>
  </TooltipRoot>
</template>