Demos
How to import
import { Tooltip } from '@fibrous/semi-ui-react';
Cautions
Tooltip finds the DOM elements its children render through a Fragment ref, which it uses to position the popup and to listen for mouse and focus events. Children can therefore be DOM nodes, function components or class components, and do not need to forward ref.
Keyboard handling (onKeyDown), tabIndex and aria-* attributes reach the children as props. When the child is a custom component, pass the props it receives on to its underlying DOM element; otherwise the trigger cannot open the popup from the keyboard and screen readers do not get the link between them.
Code
import React from 'react';
import { Tooltip, Space } from '@fibrous/semi-ui-react';
const style = { border: '2px solid var(--semi-color-border)', paddingLeft: 4, paddingRight: 4, borderRadius: 4 };
// Pass props to the underlying DOM and bind the ref (a plain prop in React 19)
function FCChildren({ ref, ...props }: React.ComponentProps<'span'>) {
return (
<span {...props} ref={ref} style={style}>
Functional Component
</span>
);
}
// React 19 cannot reach a class component's DOM node, so wrap it in a real DOM node
class MyComponent extends React.Component {
render() {
return (
<span {...this.props} style={style}>
ClassComponent
</span>
);
}
}
export default function TooltipDemo() {
return (
<Space>
<Tooltip content="semi design">
<FCChildren />
</Tooltip>
<Tooltip content="semi design">
<span>
<MyComponent />
</span>
</Tooltip>
<Tooltip content="semi design">
<span style={style}>DOM</span>
</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
import { Tooltip, Tag, type TooltipPosition as Position } from '@fibrous/semi-ui-react';
export default function TooltipDemo() {
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'],
];
return (
<div>
<div style={{ marginLeft: 80, whiteSpace: 'nowrap' }}>
{tops.map(([position, label], index) => (
<Tooltip
content={
<article>
<p>hi bytedance</p>
<p>hi bytedance</p>
</article>
}
arrowPointAtCenter={false}
position={position}
key={index}
>
<Tag style={{ margin: 8, padding: 20 }}>{label}</Tag>
</Tooltip>
))}
</div>
<div style={{ width: 80, float: 'left' }}>
{lefts.map(([position, label], index) => (
<Tooltip
content={
<article>
<p>hi bytedance</p>
<p>hi bytedance</p>
</article>
}
arrowPointAtCenter={false}
position={position}
key={index}
>
<Tag style={{ margin: 8, padding: 20, width: 60 }}>{label}</Tag>
</Tooltip>
))}
</div>
<div style={{ width: 40, marginLeft: 300 }}>
{rights.map(([position, label], index) => (
<Tooltip
content={
<article>
<p>hi bytedance</p>
<p>hi bytedance</p>
</article>
}
arrowPointAtCenter={false}
position={position}
key={index}
>
<Tag style={{ margin: 8, padding: 20, width: 60 }}>{label}</Tag>
</Tooltip>
))}
</div>
<div style={{ marginLeft: 80, clear: 'both', whiteSpace: 'nowrap' }}>
{bottoms.map(([position, label], index) => (
<Tooltip
content={
<article>
<p>hi bytedance</p>
<p>hi bytedance</p>
</article>
}
arrowPointAtCenter={false}
position={position}
key={index}
>
<Tag style={{ margin: 8, padding: 20, width: 60 }}>{label}</Tag>
</Tooltip>
))}
</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
import { Tooltip, Button } from '@fibrous/semi-ui-react';
export default function TooltipDemo() {
return (
<>
<div>
<Tooltip position="topLeft" content="semi design tooltip">
<Button type="secondary" style={{ marginRight: 8 }}>
指向元素中心
</Button>
</Tooltip>
</div>
<div style={{ marginTop: 20 }}>
<Tooltip content="semi design tooltip" arrowPointAtCenter={false} position="topLeft">
<Button type="secondary" style={{ marginRight: 8, width: 120 }}>
边缘对齐
</Button>
</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
import { useState } from 'react';
import { Tooltip, Button, Input, RadioGroup, Radio } from '@fibrous/semi-ui-react';
export default function TooltipDemo() {
const [visible, setVisible] = useState(false);
return (
<div>
<Tooltip content="hi bytedance">
<Button theme="solid" type="tertiary" style={{ marginBottom: 20 }}>
悬停显示
</Button>
</Tooltip>
<br />
<Tooltip content="hi bytedance" trigger="click">
<Button style={{ marginBottom: 20 }}>点击显示</Button>
</Tooltip>
<br />
<Tooltip content="hi bytedance" trigger="focus">
<Input aria-label="聚焦显示" style={{ width: 100, marginBottom: 20 }} placeholder="聚焦显示" />
</Tooltip>
<br />
<Tooltip content="hi bytedance" trigger="contextMenu">
<Button theme="solid" type="secondary" style={{ marginBottom: 20 }}>
右键点击展示
</Button>
</Tooltip>
<br />
<Tooltip content="hi bytedance" trigger="custom" visible={visible}>
<span style={{ display: 'inline-block' }}>
<RadioGroup type="button" onChange={(e) => setVisible(e.target.value)} value={visible}>
<Radio value>受控显示</Radio>
<Radio value={false}>受控隐藏</Radio>
</RadioGroup>
</span>
</Tooltip>
</div>
);
}Conditional trigger (condition)
When condition={false}, Tooltip will not respond to hover/click/focus triggers (does not affect trigger='custom'); 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 owner sets visible to false.
Code
import { useMemo, useState } from 'react';
import { Tooltip, Button, Switch, Space, Typography } from '@fibrous/semi-ui-react';
export default function TooltipDemo() {
const [enabled, setEnabled] = useState(true);
const content = useMemo(() => `condition is ${enabled ? 'true' : 'false'}`, [enabled]);
return (
<Space align="center">
<Typography.Text id="tooltip-condition-label">condition</Typography.Text>
<Switch aria-labelledby="tooltip-condition-label" checked={enabled} onChange={(v) => setEnabled(v)} />
<Tooltip content={content} trigger="hover" condition={enabled}>
<Button>Hover me</Button>
</Tooltip>
<Tooltip content={content} trigger="click" condition={enabled}>
<Button>Click me</Button>
</Tooltip>
</Space>
);
}Override Style
Configure specific styles for the pop-up layer through the className and style API, such as overriding the default maxWidth (240px)
Code
import { Tooltip, Tag } from '@fibrous/semi-ui-react';
export default function TooltipDemo() {
return (
<div>
<Tooltip
style={{
maxWidth: 320,
}}
className="another-classname"
content="hi semi semi semi semi semi semi semi"
>
<Tag style={{ marginRight: '8px' }}>Custom Style And ClassName</Tag>
</Tooltip>
</div>
);
}Use with Popver or Popconfirm
Tooltip, Popconfirm, and Popover listen for their trigger events (mouseenter / mouseleave / click ....) on the DOM of the trigger element, so both layers still open when they are nested directly.
The inner component discards the aria-describedby, data-popupid and arrow key handling added by the outer one, though: screen readers do not read the outer description, and keyboard focus does not open an outer popup triggered by hover.
When you need these, add a layer of elements (div or span) in the middle as the trigger of the outer one.
Code
import { Tooltip, Popconfirm, Button } from '@fibrous/semi-ui-react';
export default function TooltipDemo() {
return (
<Popconfirm content="是否确认删除" title="确认" style={{ width: 320 }}>
<span style={{ display: 'inline-block' }}>
<Tooltip content="删除评价">
<Button type="danger">删除</Button>
</Tooltip>
</span>
</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
import { Typography } from '@fibrous/semi-ui-react';
export default function TooltipDemo() {
const { Paragraph, Title, Text } = Typography;
return (
<div>
<Title heading={5} ellipsis={{ showTooltip: true }} style={{ width: 250 }}>
是一个很长很长很长很长5号标题
</Title>
<br />
<Text link ellipsis={{ showTooltip: true, pos: 'middle' }} style={{ width: 150 }}>
是一个很长很长很长很长的链接
</Text>
<br />
<br />
<Paragraph ellipsis={{ rows: 3, showTooltip: { type: 'popover', opts: { style: { width: 300 } } } }} style={{ width: 300 }}>
多行截断,展示 Popover:Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。
</Paragraph>
</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; not called for an open that is closed before the enter animation ends. | Type() => void | - |
| aria-label | aria-label of the popup when its role is dialog; ignored for other roles. | string | - |
| aria-labelledby | aria-labelledby of the popup when its role is dialog; ignored for other roles. | string | - |
| 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 |
| children | The trigger. Use a DOM element, or a function component that forwards its props and ref to its root DOM node. Text and other content is wrapped in a span that serves as the trigger. | React.ReactNode | - |
| className | 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 set to custom, only onEscKeyDown is called 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 set to 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 | Popup content: a node, or a function returning one. The function receives initialFocusRef; attach its ref to the element that takes focus once the popup opens. | React.ReactNode | RenderContent | - |
| disableArrowKeyDown | Whether the arrow keys on the trigger are prevented from moving focus into the popup. | boolean | false |
| disableFocusListener | When trigger is hover, whether keyboard focus is ignored instead of opening the popup. | 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 with the native MouseEvent when the mouse is pressed outside of the trigger and the open popup. Only for trigger click, contextMenu or custom, or with clickTriggerToHide. | Type(e: MouseEvent) => void | - |
| onEscKeyDown | Called with the native KeyboardEvent when Esc is pressed while the popup is open and closeOnEsc is true. | Type(e: 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 with whether the popup is shown when its visibility changes. With a controlled visible it reports only requests from user interaction, not changes the caller makes to visible. | Type(visible: boolean) => void | - |
| position | Position of the popup relative to the trigger: top, topLeft, topRight, left, leftTop, leftBottom, right, rightTop, rightBottom, bottom, bottomLeft or bottomRight. | Position | "top" |
| prefixCls | Class name prefix of the popup wrapper. A custom value drops the Tooltip styles. | string | "semi-tooltip" |
| preventScroll | Whether focusing the element that initialFocusRef points at, when the popup opens, avoids scrolling it into view. | boolean | - |
| ref | Handle of the component; its methods are listed under Methods below. | TypeReact.Ref<TooltipHandle> | - |
| returnFocusOnClose | Whether focus returns to the trigger after the popup closes. Has no effect with trigger set to custom. | boolean | false |
| role | ARIA role of the popup. | string | "tooltip" |
| showArrow | Whether the arrow is shown. A node renders as a custom arrow. | boolean | React.ReactNode | 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 | Inline style of the popup. The arrow color reads background-color of an object style only. | React.CSSProperties | - |
| trigger | When the popup shows: hover, focus, click, contextMenu or custom. With custom, visible alone decides. | Trigger | "hover" |
| visible | Whether the popup is shown (controlled). When given, the trigger, outside clicks and Esc only request a change through onVisibleChange, which takes effect once the caller writes it back. | 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 trigger is wrapped in a span so that it still triggers the popup. | boolean | true |
Methods
| Method | Description | Type |
|---|---|---|
| focusTrigger | Focuses the trigger. Only works when returnFocusOnClose is true and trigger is not custom. | Type() => void |
| getPopupId | Returns the id of the popup. | Type() => string |
| hide | Closes the popup at once without moving focus and calls onVisibleChange(false). With a controlled visible it only calls onVisibleChange(false), and the popup closes once the caller writes 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: undeclared attributes are not passed to the popup, and className and style land on the popup. The trigger keeps its own attributes and gets the trigger attributes added by Tooltip, such as aria-describedby, data-popupid and tabIndex.
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 description tooltip children
/* eslint-disable */
<Tooltip content={<p id="description">Edit your setting</p>}>
<IconSetting aria-label="Settings"></IconSetting>
</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).