Demos
How to import
<script lang="ts">
import { Tooltip } from '@fibrae/semi-ui-svelte';
</script>
Cautions
Tooltip applies the trigger attributes and event listeners to the trigger element, and positions the popup against the real DOM element of that trigger. The trigger can be written in two ways:
- The
childsnippet:{#snippet child({ props })}<button {...props}>…</button>{/snippet}.propscarriesdata-popupid,tabindex,aria-describedbyand an attachment that hands the element to Tooltip, and must be spread as a whole on an actual DOM element or on a component that passes the attributes to its root DOM element. Use this form for focusable triggers such as Button or Input - The content inside the tag (
children): text or other content is wrapped in Tooltip's own span (semi-tooltip-trigger-wrapper) that becomes the trigger. When the span holds a focusable element it leaves the Tab order and a warning is logged in development
content is a snippet too: write {#snippet content()}...{/snippet} inside the Tooltip tag.
Code
<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
The direction and alignment position of the popup layer can be configured through position. For detailed optional values of position, please refer to the API document below
When configured as topLeft, it pops up, and the popup layer is left-aligned with the children (when arrowPointAtCenter=false) . When configured as topRight, it pops up, and the popup layer is right-aligned with the children (when arrowPointAtCenter=false) . Same for other directions
Code
<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>Arrow Point at Center
By default arrowPointAtCenter=true, the small triangle always points to the center of the children element.
You can set it to false and the little triangle will no longer keep pointing to the center of the element. The popover is aligned to the edges of the children
Code
<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>Trigger Timing
- Configure the timing of the trigger display, the default is
hover, optionalhover/focus/click/custom/contextMenu. - When set to
custom, it needs to be used in conjunction with thevisibleattribute, at which point the display is completely controlled - The
contextMenu(right-click) trigger is available
Code
<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>Conditional trigger (condition)
When condition={false}, Tooltip will not respond to hover/click/focus triggers (with trigger="custom", visible still shows it); when condition changes from true to false, an open popup closes at once; with visible controlled it only calls onVisibleChange(false), and the popup closes once the caller writes visible back.
Code
<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>Override Style
Configure specific styles for the pop-up layer through class and style (a string), such as overriding the default maxWidth (240px)
Code
<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>Use with Popver or Popconfirm
Tooltip, Popconfirm, and Popover all bind related events on the trigger (mouseenter / mouseleave / click ....) and add trigger attributes such as aria-describedby and data-popupid.
When the outer component receives plain content as children, it is wrapped in the outer component's own span, which cannot describe the focusable element inside it, and a warning is logged in development.
To keep each component's own trigger attributes, add a layer of elements (div or span) in the middle as the trigger of the outer one: the outer component spreads props on it through the child snippet and sets tabindex={-1}, and the inner one spreads props on the real trigger through its own child snippet.
Code
<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>Show Tooltip only when the content overflows
Semi provides the Typography component for this scenario, so you do not need to check for overflow yourself before showing a Tooltip. For details, see the Typography documentation.
是一个很长很长很长很长5号标题
是一个很长很长很长很长的链接
多行截断,展示 Popover:Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。
Code
<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 Reference
Props
| Property | Description | Type | Default |
|---|---|---|---|
| afterClose | Called after the popup has closed and its leave animation has finished. | Type() => void | - |
| afterOpen | Called after the popup has opened and its enter animation has finished, or right after opening when there is no enter animation, controlled or not; not called for an open that is closed before the enter animation ends. | Type() => void | - |
| aria-describedby | aria-describedby of the popup when its role is dialog. | string | - |
| aria-label | aria-label of the popup when its role is dialog. | string | - |
| aria-labelledby | aria-labelledby of the popup when its role is dialog. | string | - |
| arrow | Snippet that replaces the default arrow while showArrow is true. | Snippet | - |
| arrowPointAtCenter | Whether the arrow points at the center of the trigger. Requires showArrow. | boolean | true |
| autoAdjustOverflow | Whether the popup flips automatically when it would be clipped. | boolean | true |
| child | Snippet that renders the trigger element itself; spread its { props } on the trigger as a whole (<button {...props}>). It wins over children. Use it for every focusable trigger such as Button or Input. | TypeSnippet<[{ props: TooltipTriggerProps }]> | - |
| children | The trigger, written inside the tag; it is wrapped in Tooltip's own span (semi-tooltip-trigger-wrapper), which suits text and content that cannot take focus. | Snippet | - |
| class | Class name of the popup. | string | - |
| clickToHide | Whether a click on the popup or anything inside it closes the popup. | boolean | - |
| clickTriggerToHide | Whether a click on the trigger closes an open popup. | boolean | - |
| closeOnEsc | Whether Esc closes the open popup. With trigger custom it only calls onEscKeyDown and the caller decides whether to close. | boolean | true |
| condition | Whether the popup may be triggered. Only an explicit false disables the hover, click and focus triggers; with trigger custom, visible still shows the popup. Changing it from true to false closes an open popup at once; with a controlled visible it only calls onVisibleChange(false), and the popup closes once the caller writes back. | boolean | true |
| content | Snippet for the popup content, with the parameter { initialFocusRef }: write {@attach initialFocusRef} on the element that should receive focus when the popup opens. | TypeSnippet<[RenderContentProps]> | - |
| disableArrowKeyDown | Whether the arrow keys on the trigger are prevented from moving focus into the popup. | boolean | false |
| disableFocusListener | Whether keyboard focus is ignored when trigger is hover. | boolean | false |
| guardFocus | Whether Tab cycles inside the popup while focus is in it. | boolean | false |
| keepDOM | Whether the popup content stays mounted while closed. | boolean | false |
| motion | Whether the popup is animated. | boolean | true |
| mouseEnterDelay | Milliseconds before the popup shows after the pointer enters. Applies to the hover and focus triggers. | number | 50 |
| mouseLeaveDelay | Milliseconds before the popup hides after the pointer leaves. Applies to the hover and focus triggers; not less than mouseEnterDelay. | number | 50 |
| onClickOutSide | Called when the mouse is pressed outside of the trigger and the open popup. Only for trigger click, contextMenu or custom, or with clickTriggerToHide. | Type(event: MouseEvent) => void | - |
| onEscKeyDown | Called when Esc is pressed while the popup is open and closeOnEsc is true. | Type(event: KeyboardEvent) => void | - |
| onOpen | Called once per opening when the popup is shown, as its enter animation starts, before afterOpen, controlled or not; not called for a hover open withdrawn because the pointer already left. | Type() => void | - |
| onVisibleChange | Called when the visibility of the popup changes; with a controlled visible it reports only requests from user interaction, not changes the parent makes to visible. | Type(visible: boolean) => void | - |
| position | Position of the popup relative to the trigger element. | Position | "top" |
| prefixCls | Class prefix of the popup. A custom value drops the Tooltip styles. | string | "semi-tooltip" |
| preventScroll | Whether focusing an element avoids scrolling it into view. | boolean | - |
| returnFocusOnClose | Whether focus returns to the trigger after the popup closes. | boolean | false |
| role | ARIA role of the popup. | string | "tooltip" |
| showArrow | Whether the arrow is shown. | boolean | true |
| spacing | Distance in pixels between the popup and the trigger. An object sets the horizontal and vertical distances. | number | { x: number; y: number } | 8 |
| stopPropagation | Whether click, mousedown and focus in/out events on the popup stop propagating. | boolean | - |
| style | Style string of the popup, written before the positioning declarations; its background-color also colors the arrow. | string | - |
| trigger | When the popup shows. With custom, visible alone decides. | 'hover' | 'focus' | 'click' | 'custom' | 'contextMenu' | "hover" |
| visible | Whether the popup is shown; bind it with bind:visible. bind:visible={get, set} makes it fully controlled, and when given this value decides. | boolean | - |
| wrapperClassName | Class name of the span that wraps the trigger when a wrapper is needed. | string | - |
| wrapperId | id of the popup element, referenced by the trigger's ARIA attributes. Generated when omitted. | string | - |
| wrapWhenSpecial | Whether a disabled or loading Semi Button, Switch or Checkbox that fills the default span gets its pointer events through the span to trigger the popup; false leaves it as it is. | boolean | true |
Methods
| Method | Description | Type |
|---|---|---|
| focusTrigger | Focuses the trigger element. Only works when returnFocusOnClose is true and trigger is not custom. | Type() => void |
| getPopupId | Returns the id of the popup element. | Type() => string |
| hide | Closes the popup. With a controlled visible it only calls onVisibleChange for the caller to write back. | Type() => void |
| rePosition | Recomputes the popup position from the trigger's current position at once. The popup already follows scrolling, window resizing, trigger moves and size changes of the trigger or popup, so this is rarely needed; following a trigger move lands a frame late, so call this to align within the same frame. | Type() => void |
Tooltip has no root DOM element: class and style (a string) land on the popup, and other undeclared attributes are discarded. The trigger attributes are added to the trigger element only. visible is bindable and there is no defaultVisible. The popup renders only after mount and is not output on the server. useTooltipDescription is not provided (the popup id is already in the props of child).
Accessibility
ARIA
- Tooltip has a tooltip role, following the definition of Tooltip in the WAI-ARIA specification
- Tooltip's content and children
- about content
- The wrapper of content will be automatically added with id attribute to match the
aria-describedbyof children and associate content with children
- The wrapper of content will be automatically added with id attribute to match the
- about children
- There should be an explicit connection between the content of the Tooltip and its children. Tooltip will automatically add the
aria-describedbyattribute to the children element, the value is the id of the content wrapper. - If the children of your Tooltip are Icon and do not contain visible text, we recommend that you add the
aria-labelattribute to the children to describe accordingly
- There should be an explicit connection between the content of the Tooltip and its children. Tooltip will automatically add the
- about content
<!-- Good practices, add aria-label to the tooltip trigger -->
<Tooltip>
{#snippet child({ props })}
<IconSetting {...props} aria-label="Settings" />
{/snippet}
{#snippet content()}<p id="description">Edit your setting</p>{/snippet}
</Tooltip>
Content Guidelines
- Only display information description and guidance, do not display error information
- Only extra links and buttons not in tooltip
- Try to simplify the description to one sentence without showing punctuation marks
Design Tokens
| Sass variable | Value | Usage |
|---|---|---|
| $animation_duration-tooltip_out | 100ms | tooltip-显示及收起动画-动画持续时间 |
| $animation_function-tooltip_out | cubic-bezier(0.215, 0.61, 0.355, 1) | tooltip-显示及收起动画-动画插值函数 |
| $horizontal-rate | 0.24 | 水平方向矫正因子 |
| $vertical-rate | 0 | 垂直方向矫正因子 |
| $motion-zoom_scale-from | 0.8 | 进出场动画的初始缩放 |
| $color-tooltip-bg-default | var(--semi-grey-7) | 工具提示默认背景色 |
| $color-tooltip-text-default | var(--semi-color-bg-0) | 工具提示默认文字颜色 |
| $color-tooltip-icon-default | var(--semi-grey-7) | 工具提示默认图标颜色 |
| $color-tooltip_arrow-icon-default | $color-tooltip-icon-default | 工具提示默认小三角箭头颜色 |
| $radius-tooltip | var(--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-tooltip | 240px | 工具提示宽度 - 默认 |
| $width-tooltip_arrow | 24px | 工具提示小三角箭头宽度 - 水平 |
| $height-tooltip_arrow | 7px | 工具提示小三角箭头高度 - 水平 |
| $height-tooltip_arrow_vertical | 24px | 工具提示小三角箭头高度 - 垂直 |
| $width-tooltip_arrow_vertical | 7px | 工具提示小三角箭头宽度度 - 垂直 |
| $spacing-tooltip_arrow_offset-x | 1px | 水平方向渲染会有缝隙,所以加个偏移量 |
| $spacing-tooltip_arrow_offset-y | 1px | 垂直方向渲染会有缝隙,所以加个偏移量 |
| $spacing-tooltip_arrow_adjusted_offset-y | 6px | 垂直方向上的校正偏移量,主要修正三角形有棱角的问题 |
| $spacing-tooltip_arrow_adjusted_offset-x | math.round($horizontal-rate * $width-tooltip_arrow) | 水平方向上的校正偏移量,主要修正三角形有棱角的问题 |
| $filter-tooltip-bg | none |
FAQ
- Why do some forms of content not wrap when the content in Tooltip is very long?
The Tooltip popup sets overflow-wrap to break-word, so an overlong word breaks onto the next line. Different language content (e.g. English, Chinese, combination of English and Chinese) needs different line breaking; if the default does not fit, adjust the line break related CSS properties through style and className (class in Vue).