@mshafiqyajid/react-tooltip
Headless tooltip hook and styled component for React. Accessible, keyboard-friendly, full placement set with -start/-end alignment, flip + shift + offset + collisionPadding, interactive mode, cursor-follow mode, touch long-press, group keyboard navigation, header/footer slots, SSR-safe, fully typed.
Playground #
import { TooltipStyled } from "@mshafiqyajid/react-tooltip/styled";
import "@mshafiqyajid/react-tooltip/styles.css";
<TooltipStyled
content="Copy to clipboard"
/>TooltipProvider — sweep / delay group
First tooltip opens after 700ms. Once one is open, sweep to the others instantly.
Install #
npm install @mshafiqyajid/react-tooltip Quick start #
import { TooltipStyled } from "@mshafiqyajid/react-tooltip/styled";
import "@mshafiqyajid/react-tooltip/styles.css";
export function App() {
return (
<TooltipStyled content="Copy to clipboard" placement="top">
<button>Copy</button>
</TooltipStyled>
);
} Placements #
Each side accepts an optional -start or -end alignment. The tooltip automatically flips to the opposite side near viewport edges (toggle with flip) and shifts back into view along the cross-axis (toggle with shift).
<TooltipStyled content="Top start" placement="top-start">...</TooltipStyled>
<TooltipStyled content="Bottom end" placement="bottom-end">...</TooltipStyled>
<TooltipStyled content="Right" placement="right" offset={12} collisionPadding={16}>
...
</TooltipStyled> Tones #
<TooltipStyled content="Neutral" tone="neutral">...</TooltipStyled>
<TooltipStyled content="Primary" tone="primary">...</TooltipStyled>
<TooltipStyled content="Success" tone="success">...</TooltipStyled>
<TooltipStyled content="Danger" tone="danger">...</TooltipStyled> Rich content #
Pass any JSX to content. Use multiline to allow text to wrap.
<TooltipStyled
multiline
content={
<span>
<strong>⌘ + C</strong> to copy
</span>
}
>
<button>Copy</button>
</TooltipStyled> Delay #
Show delay defaults to 0 (instant). Increase for less sensitive triggers. Wrap tooltips in TooltipProvider to set a shared delay and get instant switching between hovered tooltips.
<TooltipStyled content="Appears after 500ms" delay={500}>...</TooltipStyled>
// Shared delay via provider
import { TooltipProvider } from "@mshafiqyajid/react-tooltip/styled";
<TooltipProvider delay={300}>
<TooltipStyled content="Fast switch">...</TooltipStyled>
<TooltipStyled content="Fast switch too">...</TooltipStyled>
</TooltipProvider> Interactive tooltip #
Set interactive to keep the tooltip open while the cursor is over it (e.g. for clickable links inside the tooltip). A 50 ms bridge prevents accidental close on cursor transition.
<TooltipStyled
interactive
content={<a href="/docs">Read the docs</a>}
>
<button>Help</button>
</TooltipStyled> Cursor-following #
Set followCursor to anchor the tooltip to the cursor position instead of the trigger. Useful for data visualisation hover labels.
<TooltipStyled content="Position: hover" followCursor>
<div className="chart-area">...</div>
</TooltipStyled> Header and footer #
<TooltipStyled
header={<strong>Keyboard shortcut</strong>}
content="Copies the selection to clipboard"
footer={<kbd>⌘ C</kbd>}
>
<button>Copy</button>
</TooltipStyled> Group navigation #
Tooltips sharing a group key are linked: pressing ↓/↑ on a focused trigger cycles to the next/previous group member. Useful for guided tours.
<TooltipStyled group="tour" content="Step 1: Click here first">
<button>Step 1</button>
</TooltipStyled>
<TooltipStyled group="tour" content="Step 2: Then click this">
<button>Step 2</button>
</TooltipStyled> Headless #
Use useTooltip to build a fully custom tooltip with your own markup and styles.
import { useTooltip } from "@mshafiqyajid/react-tooltip";
function MyTooltip({ content, children }) {
const { triggerProps, tooltipProps, placement, isVisible } = useTooltip({
placement: "top",
delay: 300,
});
return (
<div style={{ position: "relative", display: "inline-flex" }}>
<div {...triggerProps}>{children}</div>
<div
{...tooltipProps}
data-placement={placement}
style={{
position: "absolute",
bottom: "calc(100% + 8px)",
left: "50%",
translate: "-50% 0",
opacity: isVisible ? 1 : 0,
transition: "opacity 140ms ease",
background: "#18181b",
color: "#fff",
padding: "4px 10px",
borderRadius: "6px",
fontSize: "0.78rem",
whiteSpace: "nowrap",
pointerEvents: "none",
}}
>
{content}
</div>
</div>
);
} API #
| Prop | Type | Default | Description |
|---|---|---|---|
| content | ReactNode | — | Tooltip text or JSX content |
| placement | top | bottom | left | right (each with optional -start / -end) | "top" | Preferred side and alignment |
| size | "sm" | "md" | "lg" | "md" | Size of the tooltip bubble |
| tone | "neutral" | "primary" | "success" | "danger" | "neutral" | Color tone |
| delay | number | 0 | Show delay in ms (overridden by TooltipProvider) |
| multiline | boolean | false | Allow content to wrap |
| disabled | boolean | false | Disable the tooltip entirely |
| offset | number | 8 | Gap between trigger and tooltip in px |
| collisionPadding | number | 8 | Viewport edge margin for flip / shift |
| flip | boolean | true | Auto-flip to opposite side near edges |
| shift | boolean | true | Push back into view along the cross-axis |
| strategy | "absolute" | "fixed" | "absolute" | Positioning strategy |
| sticky | boolean | false | Keep tooltip open while cursor is over it (deprecated alias for interactive) |
| interactive | boolean | false | Keep tooltip open while cursor is over the tooltip body; enables pointer-events inside the tooltip |
| followCursor | boolean | false | Tooltip follows cursor position instead of anchoring to the trigger |
| header | ReactNode | — | Optional content rendered above the main content |
| footer | ReactNode | — | Optional content rendered below the main content |
| longPressDelay | number | 500 | Long-press delay in ms for touch trigger. Set 0 to disable touch |
| group | string | — | Group key linking tooltips for ↓/↑ keyboard cycling |
| groupId | string | — | Optional id within the group for ordering (falls back to DOM order) |