展示类

工具提示

工具提示用于对一个元素进行标识或者附上少量辅助信息,最典型的场景是向用户解释图标的含义、展示被截断的文本、显示图片的描述等。

代码演示

如何引入

<script lang="ts">
    import { Tooltip } from '@fibrae/semi-ui-svelte';
</script>

注意事项

Tooltip 需要将触发属性和事件监听器应用到触发元素上,并以触发元素的真实 DOM 元素计算弹出层的定位。触发元素有两种写法:

  1. child snippet:{#snippet child({ props })}<button {...props}>…</button>{/snippet},props 带 data-popupid、tabindex、aria-describedby 和取元素引用的 attachment,必须整体展开到真实 DOM 元素或能把属性落到根 DOM 元素的组件上。Button、Input 等需要焦点的触发元素都应使用这种写法
  2. 标签内的内容(children):文本或其他内容会被包进 Tooltip 自己的 span(semi-tooltip-trigger-wrapper)作为触发元素;span 里有可聚焦元素时,span 退出 Tab 顺序,并在开发环境输出警告

content 也是 snippet:{#snippet content()}...{/snippet},写在 Tooltip 标签内即可。

Component
Text DOM
代码
<script lang="ts">
    import { Space, Tag, Tooltip } from '@fibrae/semi-ui-svelte';

    const style = 'border: 2px solid var(--semi-color-border); padding-left: 4px; padding-right: 4px; border-radius: 4px';
</script>

<Space>
    <Tooltip>
        <Tag {style}>Component</Tag>
        {#snippet content()}semi design{/snippet}
    </Tooltip>
    <Tooltip>
        Text
        {#snippet content()}semi design{/snippet}
    </Tooltip>
    <Tooltip>
        {#snippet child({ props })}
            <span {...props} {style}>DOM</span>
        {/snippet}
        {#snippet content()}semi design{/snippet}
    </Tooltip>
</Space>

位置

可以通过 position 配置弹出层方向以及对齐位置,position 详细可选值请参考下方 API 文档
配置为 top 时 向上弹出
配置为 topLeft 时,向上弹出,且弹出层与 children 左对齐(当arrowPointAtCenter=false时)
配置为 topRight 时,向上弹出,且弹出层与 children 右对齐(当arrowPointAtCenter=false时)
其他方向同理

TL
Top
TR
LT
Left
LB
RT
Right
RB
BL
Bottom
BR
代码
<script lang="ts">
    import { Tag, Tooltip, type Position } from '@fibrae/semi-ui-svelte';

    const tops: [Position, string][] = [
        ['topLeft', 'TL'],
        ['top', 'Top'],
        ['topRight', 'TR'],
    ];
    const lefts: [Position, string][] = [
        ['leftTop', 'LT'],
        ['left', 'Left'],
        ['leftBottom', 'LB'],
    ];
    const rights: [Position, string][] = [
        ['rightTop', 'RT'],
        ['right', 'Right'],
        ['rightBottom', 'RB'],
    ];
    const bottoms: [Position, string][] = [
        ['bottomLeft', 'BL'],
        ['bottom', 'Bottom'],
        ['bottomRight', 'BR'],
    ];
</script>

{#snippet bubble()}
    <article>
        <p>hi bytedance</p>
        <p>hi bytedance</p>
    </article>
{/snippet}

<div>
    <div style="margin-left: 80px; white-space: nowrap">
        {#each tops as [position, label] (position)}
            <Tooltip arrowPointAtCenter={false} content={bubble} {position}>
                <Tag style="margin: 8px; padding: 20px">{label}</Tag>
            </Tooltip>
        {/each}
    </div>
    <div style="width: 80px; float: left">
        {#each lefts as [position, label] (position)}
            <Tooltip arrowPointAtCenter={false} content={bubble} {position}>
                <Tag style="margin: 8px; padding: 20px; width: 60px">{label}</Tag>
            </Tooltip>
        {/each}
    </div>
    <div style="width: 40px; margin-left: 300px">
        {#each rights as [position, label] (position)}
            <Tooltip arrowPointAtCenter={false} content={bubble} {position}>
                <Tag style="margin: 8px; padding: 20px; width: 60px">{label}</Tag>
            </Tooltip>
        {/each}
    </div>
    <div style="margin-left: 80px; clear: both; white-space: nowrap">
        {#each bottoms as [position, label] (position)}
            <Tooltip arrowPointAtCenter={false} content={bubble} {position}>
                <Tag style="margin: 8px; padding: 20px; width: 60px">{label}</Tag>
            </Tooltip>
        {/each}
    </div>
</div>

指向元素中心

默认情况下 arrowPointAtCenter=true,小三角始终指向 children 元素中心位置。你可以将其设置为 false,此时小三角将不再保持指向元素中心。弹出层与 children 边缘对齐

代码
<script lang="ts">
    import { Button, Tooltip } from '@fibrae/semi-ui-svelte';
</script>

<div>
    <Tooltip position="topLeft">
        {#snippet child({ props })}
            <Button {...props} style="margin-right: 8px" type="secondary">指向元素中心</Button>
        {/snippet}
        {#snippet content()}semi design tooltip{/snippet}
    </Tooltip>
</div>

<div style="margin-top: 20px">
    <Tooltip arrowPointAtCenter={false} position="topLeft">
        {#snippet child({ props })}
            <Button {...props} style="margin-right: 8px; width: 120px" type="secondary">边缘对齐</Button>
        {/snippet}
        {#snippet content()}semi design tooltip{/snippet}
    </Tooltip>
</div>

触发时机

  • 配置触发展示的时机,默认为 hover,可选 hover/focus/click/custom/ 'contextMenu'
  • 设为 custom 时,需要配合 visible 属性使用,此时显示与否完全受控
  • contextMenu 为右键触发




代码
<script lang="ts">
    import { Button, Input, Radio, RadioGroup, Tooltip, type RadioValue } from '@fibrae/semi-ui-svelte';

    let visible = $state<RadioValue | undefined>(0);
</script>

{#snippet text()}hi bytedance{/snippet}

<div>
    <Tooltip content={text}>
        {#snippet child({ props })}
            <Button {...props} style="margin-bottom: 20px" theme="solid" type="tertiary">悬停显示</Button>
        {/snippet}
    </Tooltip>
    <br />
    <Tooltip content={text} trigger="click">
        {#snippet child({ props })}
            <Button {...props} style="margin-bottom: 20px">点击显示</Button>
        {/snippet}
    </Tooltip>
    <br />
    <Tooltip content={text} trigger="focus">
        {#snippet child({ props })}
            <Input {...props} aria-label="聚焦显示" placeholder="聚焦显示" style="width: 100px; margin-bottom: 20px" />
        {/snippet}
    </Tooltip>
    <br />
    <Tooltip content={text} trigger="contextMenu">
        {#snippet child({ props })}
            <Button {...props} style="margin-bottom: 20px" theme="solid" type="secondary">右键点击展示</Button>
        {/snippet}
    </Tooltip>
    <br />
    <Tooltip content={text} trigger="custom" visible={visible === 1}>
        {#snippet child({ props })}
            <span {...props} style="display: inline-block" tabindex="-1">
                <RadioGroup bind:value={visible} type="button">
                    <Radio value={1}>受控显示</Radio>
                    <Radio value={0}>受控隐藏</Radio>
                </RadioGroup>
            </span>
        {/snippet}
    </Tooltip>
</div>

condition 条件触发

当 condition={false} 时,Tooltip 不响应 hover/click/focus 等触发行为(trigger="custom" 时仍按 visible 显示);condition 由 true 变为 false 时,已显示的弹出层立即关闭;visible 受控时只调用 onVisibleChange(false),使用方回写 visible 后才关闭。

condition
代码
<script lang="ts">
    import { Button, Space, Switch, Tooltip, TypographyText } from '@fibrae/semi-ui-svelte';

    let enabled = $state(true);
    const text = $derived(`condition is ${enabled ? 'true' : 'false'}`);
</script>

<Space align="center">
    <TypographyText id="tooltip-condition-label">condition</TypographyText>
    <Switch aria-labelledby="tooltip-condition-label" bind:checked={enabled} />
    <Tooltip condition={enabled} trigger="hover">
        {#snippet child({ props })}
            <Button {...props}>Hover me</Button>
        {/snippet}
        {#snippet content()}{text}{/snippet}
    </Tooltip>
    <Tooltip condition={enabled} trigger="click">
        {#snippet child({ props })}
            <Button {...props}>Click me</Button>
        {/snippet}
        {#snippet content()}{text}{/snippet}
    </Tooltip>
</Space>

覆盖特定样式

你可以通过 class、style(字符串)为弹出层配置特定样式,例如覆盖默认的 maxWidth (240px)

Custom Style And ClassName
代码
<script lang="ts">
    import { Tag, Tooltip } from '@fibrae/semi-ui-svelte';
</script>

<div>
    <Tooltip class="another-classname" style="max-width: 320px">
        <Tag style="margin-right: 8px">Custom Style And ClassName</Tag>
        {#snippet content()}hi semi semi semi semi semi semi semi{/snippet}
    </Tooltip>
</div>

搭配 Popover 或 Popconfirm 使用

Tooltip、Popconfirm、Popover 都会在触发元素上绑定相关事件(mouseenter/mouseleave/click....),并注入 aria-describedby、data-popupid 等触发属性。
外层以普通内容作为 children 时,会被包进外层自己的 span 里,这个 span 无法描述其中可聚焦的元素,开发环境下会输出警告。
需要保持各自的触发属性时,在中间加一层元素(div 或 span)作为外层的触发元素:外层用 child snippet 把 props 展开到它上面并设 tabindex={-1},内层用 child snippet 把 props 展开到真正的触发元素上。

代码
<script lang="ts">
    import { Button, Popconfirm, Tooltip } from '@fibrae/semi-ui-svelte';
</script>

<Popconfirm style="width: 320px">
    {#snippet child({ props })}
        <span {...props} style="display: inline-block" tabindex="-1">
            <Tooltip>
                {#snippet child({ props })}
                    <Button {...props} type="danger">删除</Button>
                {/snippet}
                {#snippet content()}删除评价{/snippet}
            </Tooltip>
        </span>
    {/snippet}
    {#snippet title()}确认{/snippet}
    {#snippet content()}是否确认删除{/snippet}
</Popconfirm>

仅当内容宽度超出时展示 Tooltip

Semi 为这种场景提供了 Typography 组件,可以更简单快捷地满足需求。不需要自己再对 Tooltip 的出现做条件判断,详细的使用请参考Typography 组件文档

是一个很长很长很长很长5号标题

是一个很长很长很长很长的链接

多行截断,展示 Popover:Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

代码
<script lang="ts">
    import { TypographyParagraph, TypographyText, TypographyTitle } from '@fibrae/semi-ui-svelte';
</script>

<div>
    <TypographyTitle ellipsis={{ showTooltip: true }} heading={5} style="width: 250px">是一个很长很长很长很长5号标题</TypographyTitle>
    <br />
    <TypographyText ellipsis={{ showTooltip: true, pos: 'middle' }} link style="width: 150px">是一个很长很长很长很长的链接</TypographyText>
    <br />
    <br />
    <TypographyParagraph ellipsis={{ rows: 3, showTooltip: { type: 'popover', opts: { style: 'width: 300px' } } }} style="width: 300px">
        多行截断,展示 Popover:Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。
    </TypographyParagraph>
</div>

API 参考


Props

属性说明类型默认值
afterClose弹出层关闭且离场动画结束后的回调。Type() => void-
afterOpen弹出层打开且进场动画结束后的回调;没有进场动画时打开后即调用,受控与非受控相同;进场结束前被关闭的打开不调用。Type() => void-
aria-describedby弹出层 role 为 dialog 时的 aria-describedby。string-
aria-label弹出层 role 为 dialog 时的 aria-label。string-
aria-labelledby弹出层 role 为 dialog 时的 aria-labelledby。string-
arrow自定义箭头的 snippet,showArrow 为 true 时替换默认箭头。Snippet-
arrowPointAtCenter箭头是否指向触发元素中心,需要同时开启 showArrow。booleantrue
autoAdjustOverflow弹出层被遮挡时是否自动调整方向。booleantrue
child渲染触发元素本身的 snippet,参数 { props } 要整体展开到触发元素上(<button {...props}>);传了它则忽略 children。可聚焦的触发元素(Button、Input 等)都用这种写法。TypeSnippet<[{ props: TooltipTriggerProps }]>-
children触发元素,写在标签内;会被包进 Tooltip 自己的 span(semi-tooltip-trigger-wrapper),适合文本与不可聚焦的内容。Snippet-
class弹出层的类名。string-
clickToHide点击弹出层及其内部任一元素时是否关闭弹出层。boolean-
clickTriggerToHide点击触发元素时是否关闭已打开的弹出层。boolean-
closeOnEsc弹出层打开期间按 Esc 是否关闭弹出层;trigger 为 custom 时只调用 onEscKeyDown,由使用方决定是否关闭。booleantrue
condition是否允许触发显示;仅显式设为 false 时,hover、click、focus 等触发行为不生效,trigger 为 custom 时仍按 visible 显示。由 true 变为 false 时立即关闭已显示的弹出层;visible 受控时只调用 onVisibleChange(false),使用方回写后才关闭。booleantrue
content弹出层内容的 snippet,参数 { initialFocusRef }:弹出层打开时需要聚焦的元素写 {@attach initialFocusRef}。TypeSnippet<[RenderContentProps]>-
disableArrowKeyDown是否禁用在触发元素上按方向键把焦点移入弹出层。booleanfalse
disableFocusListenertrigger 为 hover 时,是否不响应键盘聚焦打开弹出层。booleanfalse
guardFocus焦点位于弹出层内时,Tab 是否在弹出层内循环。booleanfalse
keepDOM关闭时是否保留弹出层内容而不销毁。booleanfalse
motion是否展示弹出层动画。booleantrue
mouseEnterDelay指针移入后延迟显示的毫秒数,仅 trigger 为 hover 或 focus 时生效。number50
mouseLeaveDelay指针移出后延迟隐藏的毫秒数,仅 trigger 为 hover 或 focus 时生效,不小于 mouseEnterDelay。number50
onClickOutSide弹出层打开时在触发元素与弹出层以外的区域按下鼠标时的回调,仅 trigger 为 click、contextMenu、custom 或开启 clickTriggerToHide 时有效。Type(event: MouseEvent) => void-
onEscKeyDowncloseOnEsc 为 true 时,弹出层打开期间按 Esc 的回调。Type(event: KeyboardEvent) => void-
onOpen弹出层显示、进场动画开始时的回调,每次打开一次,早于 afterOpen,受控与非受控相同;悬停打开后指针已离开而撤回的打开不调用。Type() => void-
onVisibleChange弹出层显示状态变化时的回调;visible 受控时只报告用户交互发起的请求,父级自己改变 visible 不调用。Type(visible: boolean) => void-
position弹出层相对触发元素的位置。Position"top"
prefixCls弹出层类名前缀,设置后弹出层不再带 Tooltip 的样式。string"semi-tooltip"
preventScroll聚焦时是否阻止浏览器滚动到被聚焦元素。boolean-
returnFocusOnClose弹出层关闭后焦点是否回到触发元素。booleanfalse
role弹出层的 ARIA role。string"tooltip"
showArrow是否显示箭头三角形。booleantrue
spacing弹出层与触发元素的距离,单位为像素;对象形式分别设置水平与垂直方向。number | { x: number; y: number }8
stopPropagation是否阻止弹出层上的 click、mousedown 与焦点进出事件冒泡。boolean-
style弹出层的样式字符串,写在定位样式之前;其中的 background-color 同时给箭头着色。string-
trigger触发显示的时机。custom 时显示与否完全由 visible 决定。'hover' | 'focus' | 'click' | 'custom' | 'contextMenu'"hover"
visible弹出层是否显示,可用 bind:visible 绑定;bind:visible={get, set} 时完全受控,传入后由该值决定显示。boolean-
wrapperClassName触发元素被 span 包裹时,该 span 的类名。string-
wrapperId弹出层节点的 id,触发元素的 aria 属性指向它;缺省时自动生成。string-
wrapWhenSpecial默认的 span 里只有一个禁用或加载中的 Semi Button、Switch 或 Checkbox 时,是否让它的指针事件经 span 触发弹出层;false 则保持原样。booleantrue

Methods

方法说明类型
focusTrigger让触发元素获得焦点,仅在 returnFocusOnClose 为 true 且 trigger 不是 custom 时生效。Type() => void
getPopupId返回弹出层节点的 id。Type() => string
hide关闭弹出层;visible 受控时只调用 onVisibleChange,由使用方回写。Type() => void
rePosition按触发器当前位置立即重新计算弹出层位置。弹出层已随滚动、窗口尺寸变化、触发器移动以及触发器或弹出层的尺寸变化自动重新定位,一般无需调用;跟随触发器移动会晚一帧,需要同一帧内对齐时调用。Type() => void

Tooltip 没有根 DOM 元素,class、style(字符串)落在弹出层上,其他未声明的属性会被丢弃;触发属性只加在触发元素上。visible 可绑定,没有 defaultVisible。弹层只在挂载后渲染,服务端不输出。不提供 useTooltipDescription(弹层 id 已在 child 的 props 里)。

Accessibility

ARIA

  • Tooltip 具有 tooltip role,遵循 WAI-ARIA 规范中对于 Tooltip 的定义
  • Tooltip 的 content 与 children
    • 关于 content
      • content 的 wrapper 会被自动添加 id 属性,用于与 children 的 aria-describedby 匹配,关联 content 与 children
    • 关于 children
      • Tooltip 的内容(content)与其触发器(children)之间应当具有显式联系。Tooltip 会自动为 children 元素添加 aria-describedby 属性,值为 content wraper的 id
      • 若你 Tooltip的children 是Icon,不包含可见文本,我们推荐你在 children 上添加 aria-label 属性进行相应描述
<!-- 推荐做法:给 Tooltip 的触发元素添加 aria-label 描述 -->
<Tooltip>
    {#snippet child({ props })}
        <IconSetting {...props} aria-label="Settings" />
    {/snippet}
    {#snippet content()}<p id="description">Edit your setting</p>{/snippet}
</Tooltip>

文案规范

  • 只展示信息说明和引导,不展示报错信息
  • 不在 tooltip 里只能是额外的链接和按钮
  • 尽量精简至一句话进行说明,不展示标点符号

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$animation_duration-tooltip_out100mstooltip-显示及收起动画-动画持续时间
$animation_function-tooltip_outcubic-bezier(0.215, 0.61, 0.355, 1)tooltip-显示及收起动画-动画插值函数
$horizontal-rate0.24水平方向矫正因子
$vertical-rate0垂直方向矫正因子
$motion-zoom_scale-from0.8进出场动画的初始缩放
$color-tooltip-bg-defaultvar(--semi-grey-7)工具提示默认背景色
$color-tooltip-text-defaultvar(--semi-color-bg-0)工具提示默认文字颜色
$color-tooltip-icon-defaultvar(--semi-grey-7)工具提示默认图标颜色
$color-tooltip_arrow-icon-default$color-tooltip-icon-default工具提示默认小三角箭头颜色
$radius-tooltipvar(--semi-border-radius-medium)工具提示默认圆角
$spacing-tooltip_content-paddingLeft$spacing-base-tight工具提示内容左侧内边距
$spacing-tooltip_content-paddingRight$spacing-base-tight工具提示内容右侧内边距
$spacing-tooltip_content-paddingTop$spacing-tight工具提示内容顶部内边距
$spacing-tooltip_content-paddingBottom$spacing-tight工具提示内容底部内边距
$font-tooltip-fontSize$font-size-regular工具提示文本字号
$width-tooltip240px工具提示宽度 - 默认
$width-tooltip_arrow24px工具提示小三角箭头宽度 - 水平
$height-tooltip_arrow7px工具提示小三角箭头高度 - 水平
$height-tooltip_arrow_vertical24px工具提示小三角箭头高度 - 垂直
$width-tooltip_arrow_vertical7px工具提示小三角箭头宽度度 - 垂直
$spacing-tooltip_arrow_offset-x1px水平方向渲染会有缝隙,所以加个偏移量
$spacing-tooltip_arrow_offset-y1px垂直方向渲染会有缝隙,所以加个偏移量
$spacing-tooltip_arrow_adjusted_offset-y6px垂直方向上的校正偏移量,主要修正三角形有棱角的问题
$spacing-tooltip_arrow_adjusted_offset-xmath.round($horizontal-rate * $width-tooltip_arrow)水平方向上的校正偏移量,主要修正三角形有棱角的问题
$filter-tooltip-bgnone

FAQ

  • 为什么 Tooltip content 配置很长很长的内容时,某些情况下内容会超出显示区域?
    Tooltip 的弹出层设置了 overflow-wrap 为 break-word,过长的单词会断开换行。不同语言内容(纯英文、中文、中英文混合、其他语种混合)对换行的需求不一致,默认设置不符合预期时,可以通过 style/className(Vue 为 style/class)设置换行相关 CSS 属性进行调整。