Tooltip
Floating hint attached to one or more anchors, rendered in a portal and positioned to stay in the viewport. The tooltip and its anchors are separate elements, tied together by an id.
Use this when / not when
- Use for a short hint about a control the user is pointing at: what an icon means, why a button is disabled, the full text of something truncated.
- Not for an explanation the user has to read to proceed — a tooltip is not reachable by
keyboard and disappears on scroll.
HelpButtonis the click-to-open equivalent. - Not for a menu of actions: that is
ContextMenuorDropDown. - Not as an accessible name. The tooltip is not linked to its anchor, so a screen reader never
reads it with the control; a control that needs a name needs an
aria-labelas well.
Import
import { Tooltip } from "@onlyoffice/apps-ui-kit/components/tooltip";
Also exported from the root barrel @onlyoffice/apps-ui-kit.
Needs ThemeProvider from @onlyoffice/apps-ui-kit/providers/theme above it in the tree for
the background and text colours.
RootTooltip, TooltipContainer and withTooltip come from the same folder and are described
under Sub-components.
Stories
Default
The basic setup: the anchor names the tooltip with data-tooltip-id and carries its text in data-tooltip-content. Hover the link to see the tooltip follow the pointer (float); change any other prop live in the Controls panel below.
Custom Styling
For a tooltip that has to stand out from the theme: hover the link to see slight transparency (opacity), a narrower width limit (maxWidth) and the arrow pointing at the link (noArrow={false}).
Click To Show
For touch screens and hints the user asks for: click the link to open the tooltip on its right and click again to close it; hovering does nothing (openOnClick).
Rich Content
For a hint that needs more than one line of plain text: hover the link to see a bold title taken from the anchor's text, with an address and a title below it (getContent).
Shared By Many Anchors
For a list where every row needs its own hint: hover each name to see one tooltip show that member's details, looked up from the index the anchor carries (getContent).
Fixed Content
For content written once in the markup rather than on each anchor: hover the link, which has no data-tooltip-content, to see the tooltip's own children.
Anchored By Selector
For anchors that cannot carry a data-tooltip-id: hover either link to see the tooltip below it; both are found by their class (anchorSelect). The selector is matched across the whole page.
Clickable Content
For a tooltip with a link inside: hover the anchor, then move the pointer into the tooltip; it stays open, so the link can be clicked (clickable).
Delayed Appearance
For anchors the pointer often crosses on its way elsewhere: rest the pointer on the link for a second before the tooltip appears; passing over it shows nothing (delayShow).
Controlled Open
For a tooltip the host decides to show, such as after a failed action: click the button to open the tooltip and again to close it; hovering no longer does either (isOpen).
Opened From Code
For a tooltip shown at a moment only the code knows: click Open to see the tooltip below it and Close to hide it; hovering either button shows nothing (imperativeModeOnly with ref).
Css Customization
Every overridable variable set on one tooltip -- the variables are listed under CSS variables on this page. The tooltip renders in a portal outside the story's markup, so the variables go on its own style prop, which lands on the wrapper around it. Hover the link to see all of them at once; the stacking order has no visible effect here.
Minimal example
One tooltip serves every anchor that names its id. The anchor carries the text.
import { Tooltip } from "@onlyoffice/apps-ui-kit/components/tooltip";
export function StorageHint() {
return (
<>
<span data-tooltip-id="storage" data-tooltip-content="12.4 GB of 20 GB">
Storage
</span>
<Tooltip id="storage" />
</>
);
}
Props
Recipes
Open and close, controlled
isOpen takes the tooltip over: it no longer opens on hover, and afterHide is where the
library reports a close it performed itself.
import { useState } from "react";
import { Button } from "@onlyoffice/apps-ui-kit/components/button";
import { Tooltip } from "@onlyoffice/apps-ui-kit/components/tooltip";
export function ExplainedAction() {
const [open, setOpen] = useState(false);
return (
<>
<span data-tooltip-id="why">
<Button label="Publish" isDisabled onClick={() => setOpen(!open)} />
</span>
<Tooltip id="why" isOpen={open} afterHide={() => setOpen(false)}>
Publishing needs at least one file in the room.
</Tooltip>
</>
);
}
Rich content, built from the anchor
getContent receives the anchor's data-tooltip-content and the element itself, so one
tooltip can render every row of a list.
import { Text } from "@onlyoffice/apps-ui-kit/components/text";
import { Tooltip } from "@onlyoffice/apps-ui-kit/components/tooltip";
export function MemberHints({ names }: { names: string[] }) {
return (
<>
{names.map((name) => (
<span key={name} data-tooltip-id="member" data-tooltip-content={name}>
{name.slice(0, 2)}
</span>
))}
<Tooltip
id="member"
place="bottom"
maxWidth="240px"
clickable
getContent={({ content }) => (
<Text fontSize="12px" fontWeight={600}>
{content}
</Text>
)}
/>
</>
);
}
Behaviour the types don't state
- The tooltip renders into
document.body, and only after mount. It goes through the kit'sPortal, so nothing of it exists during a server render and its position in your JSX does not affect where it appears. - Anchors are found by attribute, across the whole document. An anchor is any element with
data-tooltip-id="<id>"; itsdata-tooltip-contentis the text.anchorSelectis the other way in, and it is matched globally too — a selector such as.rowwill pick up rows rendered by anyone. - It closes on Escape, on scroll, on resize and on a click outside the anchor, all four
always on. A tooltip cannot be kept open across a scroll. The one exception is
imperativeModeOnly, which turns all four off: such a tooltip closes only when code callsclose(). openOnClickreplaces hover rather than adding to it. With it set, hovering does nothing and a second click closes the tooltip.coloris applied in an effect and never removed. Passing it once and clearing it later leaves the last colour on the element; the CSS variables below are the way back.styleandclassNameland on the wrapper the portal renders, not on the tooltip — the tooltip's own style istooltipStyle, and its width ismaxWidth.placeis a preference: the tooltip flips through twelve fallback placements and shifts along the axis to stay on screen, so it can end up anywhere.- The default offset is 4px and the default max width 320px; the system tooltip described below is allowed 800px instead.
Sub-components
RootTooltip
Mounts two shared tooltips into #root, or document.body if there is none, and publishes the
first on window.__systemTooltipRef:
system-tooltip— imperative only, opened bywithTooltipfrom the hover of a wrapped component;info-tooltip— opens on click, for anchors that point at it by id.
Nothing in the kit mounts it. A title on any component wrapped in withTooltip —
Button among them — silently does nothing until you render
<RootTooltip /> once, near the root of the application.
import { RootTooltip } from "@onlyoffice/apps-ui-kit/components/tooltip";
export function AppTooltips() {
return <RootTooltip />;
}
withTooltip and TooltipContainer
withTooltip(Component) returns the component with title and tooltipContent added, which it
consumes: it attaches hover handlers, creates a 1×1 hidden anchor at the pointer and opens the
system tooltip after 700ms. TooltipContainer is the same wrapper around a plain element,
useful for giving a hint to markup of your own.
- Only a string produces a tooltip.
tooltipContentof any other node type leaves the element with no tooltip at all, andtooltipContentwins overtitlewhen both are given. tooltipPlaceandtooltipFitToContenthave been removed fromWithTooltipProps: they were declared, stripped before forwarding and read nowhere, so passing one now fails to compile rather than doing nothing.omitTooltipPropsstill strips them, so a caller that has not caught up does not put them on the DOM. The placement is the system tooltip'sbottom-start.- Under
NODE_ENV=testthe wrapper skips all of this and renders a nativetitleattribute instead, so a test asserts ontitle, not on a floating element. - The handlers you pass are called in addition to the wrapper's own, except
onMouseMove, which is dropped once a tooltip is active.
CSS variables
Set these on an ancestor of the tooltip's wrapper — it is in the portal, so :root is the
usual place — or on the wrapper itself through the style prop, which is the way to reach one
tooltip only.
The width cap is never wider than the window: it is min(100vw, …) of the value.
--tooltip-max-width is not an override: the stylesheet declares it on the wrapper, so one set
on an ancestor is ignored, and it is what maxWidth writes. --tooltip-max-width-value wins
over both.
Accessibility
- The floating element has
role="tooltip", set by react-tooltip, so it is announced as a tooltip once it is shown. Nothing ties it to the anchor, though: there is noaria-describedbyand no id relationship, so a screen reader does not read it with the anchor. - It opens on hover and on click, never on focus, so a keyboard user cannot see it at all.
Anything a user must know belongs in the text or in an
aria-label. - Escape closes it, which is the one convention it does follow — except under
imperativeModeOnly. clickablekeeps it open while the pointer is inside, which is required if it contains a link — without it the link cannot be reached.
Test ids
| Element | data-testid |
|---|---|
| The wrapper around a tooltip | tooltip, overridable with dataTestId |
RootTooltip's system tooltip | system-tooltip-container |
RootTooltip's info tooltip | info-tooltip-container |
Related
Portal— how the tooltip leaves the flow of your markup.HelpButton— a hint the user opens deliberately.IconButton—tooltipIdandtooltipContenton it render one of these.