A thoughtfully crafted tooltip library for React—simple to use, delightful to experience.
Tooltips seem simple, but getting them right takes care. They should appear when needed, stay out of the way when not, and feel natural as users navigate your interface.
React Tip Magic handles the details so you don't have to—positioning, transitions, accessibility, keyboard support—all with a clean, declarative API.
npm install @galangel/react-tip-magicimport{TipMagicProvider}from'@galangel/react-tip-magic';import'@galangel/react-tip-magic/styles.css';functionApp(){return(<TipMagicProvider><YourApp/></TipMagicProvider>);}That's it. Now add data-tip to any element:
<buttondata-tip="Save your changes">Save</button>The core of React Tip Magic. One tooltip instance that gracefully moves between elements, providing a smooth, cohesive experience.
<buttondata-tip="Click to save">Save</button><buttondata-tip="Undo last action">Undo</button>Display shortcuts alongside your tooltips using the data-tip-shortcut attribute:
<buttondata-tip="Copy"data-tip-shortcut="⌘C">Copy</button><buttondata-tip="Paste"data-tip-shortcut="⌘V">Paste</button><buttondata-tip="Save"data-tip-shortcut="⌘S">Save</button>{/* Position control */}<buttondata-tip="Below the button"data-tip-placement="bottom">
Hover me
</button>;{/* Smooth transitions between grouped elements */}<nav><adata-tip="Home"data-tip-movedata-tip-group="nav">
Home
</a><adata-tip="About"data-tip-movedata-tip-group="nav">
About
</a><adata-tip="Contact"data-tip-movedata-tip-group="nav">
Contact
</a></nav>;{/* Interactive tooltips that stay visible on hover */}<spandata-tip="Click <a href='#'>here</a> to learn more"data-tip-htmldata-tip-interactive>
More info
</span>;Walk users through your interface with step-by-step tours. Helpful for onboarding, feature introductions, or contextual guidance.
import{useTour}from'@galangel/react-tip-magic';functionApp(){consttour=useTour({steps: [{target: 'dashboard',title: 'Welcome',content: 'This is your dashboard.'},{target: 'create-btn',title: 'Create',content: 'Click here to get started.'},],// Built-in Back/Next controls are off by default—turn them on for multi-step toursnavigation: {showControls: true},progress: {show: true},});return(<div><navdata-tip-id="dashboard">Dashboard</nav><buttondata-tip-id="create-btn"onClick={tour.start}>
New Project
</button></div>);}useTour is headless by default: leave navigation.showControls unset and render your own controls from the returned next, prev, end and progress.
consttour=useTour({ steps });{tour.isActive&&(<div><span>{tour.progress.current} of {tour.progress.total}</span><buttononClick={tour.prev}disabled={tour.currentStep?.isFirst}>
Back
</button><buttononClick={tour.next}>{tour.currentStep?.isLast ? 'Finish' : 'Next'}</button></div>);}Step content is injected as HTML so that titles, media and controls work. Use text instead when the value is plain text the library should escape for you:
{target: 'profile',text: `Signed in as ${user.name}`}Exactly one of content or text is required — a step with neither, or with both, won't compile. For markup with an interpolated value, keep content and escape the value with the exported escapeHtml:
import{escapeHtml}from'@galangel/react-tip-magic';{target: 'profile',content: `Signed in as <b>${escapeHtml(user.name)}</b>`}start() returns false and changes nothing—no onStart, no onStepChange—if no step's target is in the DOM, so a tour that can't render never reports itself as shown. Supply onTargetMissing to handle it yourself (and to keep the library off console.warn):
consttour=useTour({
steps,onTargetMissing: (step)=>{logger.warn('tour target missing',{target: step.target});return'skip';// or end the tour, which is the default},});if(tour.start()){markTourAsSeen();}Recovery depends on the direction of travel: next() skips or ends the tour, while prev() and goTo() leave it where it is — the step on screen is still fine. goTo() never skips, so it lands on the step you asked for or returns false.
A tour panel is a dialog, so focus moves into it when a step opens. autoFocus picks which element inside it:
consttour=useTour({
steps,// 'panel' (default) | 'primary' — Next/Finish, so Enter advances | false — leave focus alonenavigation: {showControls: true,autoFocus: 'primary'},});'primary' falls back to the panel when a step renders no Next/Finish button — never to the close button. Overridable per step via step.navigation.autoFocus.
Tours also support progress indicators, keyboard support, and backdrop highlighting (focus: true, or focus: { dismissOnClick: true } to let a click on the backdrop end the tour)—all configurable to fit your needs.
A keyboard shortcut discovery menu. Press a key (F1 by default) to reveal all available shortcuts in your interface, with fuzzy search to quickly find what you need.
import{TipAdvisor}from'@galangel/react-tip-magic';functionApp(){return(<TipMagicProvider><divclassName="toolbar"><buttondata-tip="Copy"data-tip-shortcut="⌘C">
Copy
</button><buttondata-tip="Paste"data-tip-shortcut="⌘V">
Paste
</button><buttondata-tip="Save"data-tip-shortcut="⌘S">
Save
</button></div>{/* Press F1 to open the advisor */}<TipAdvisor/></TipMagicProvider>);}Features:
- Fuzzy search with highlighted matches
- Keyboard navigation (arrow keys + Enter)
- Hover to preview tooltip locations
- Click to trigger the associated element
| Attribute | Description | Example |
|---|---|---|
data-tip | Tooltip content | data-tip="Hello" |
data-tip-shortcut | Keyboard shortcut badge | data-tip-shortcut="⌘S" |
data-tip-id | Element identifier for tours | data-tip-id="welcome" |
data-tip-placement | Position (top, bottom, left, right) | data-tip-placement="bottom" |
data-tip-delay | Show delay in ms | data-tip-delay="500" |
data-tip-hide-delay | Hide delay in ms | data-tip-hide-delay="100" |
data-tip-disabled | Disable tooltip | data-tip-disabled |
data-tip-html | Parse content as HTML | data-tip-html |
data-tip-interactive | Keep tooltip on hover | data-tip-interactive |
data-tip-move | Smooth move transition | data-tip-move |
data-tip-group | Group for transitions | data-tip-group="nav" |
data-tip-no-arrow | Hide tooltip arrow | data-tip-no-arrow |
For more control, use the useTipMagic hook:
import{useTipMagic}from'@galangel/react-tip-magic';functionMyComponent(){const{ tooltip }=useTipMagic();consthandleClick=()=>{tooltip.show('#my-element','Dynamic content');};return(<buttonid="my-element"onClick={handleClick}>
Click me
</button>);}- React 18+ with modern hooks
- TypeScript for type safety
- Floating UI for positioning
- Storybook for documentation
Apache-2.0
Made with care for the React community
