@tour-kit/hintsHeadless
HintHeadless
HeadlessHint: unstyled hint wrapper exposing visibility state and dismiss actions via render props for custom UIs
HintHeadless
The main headless hint component that manages state, positioning, and provides render props for complete UI customization.
Why Use HintHeadless?
- Complete control - Build any hint UI you want
- All logic included - State, positioning, dismissal handled
- Render props - Access all internal state for custom rendering
- Same accessibility - ARIA attributes built in
Basic Usage
import { HintHeadless } from '@tour-kit/hints/headless';
function CustomHint() {
return (
<HintHeadless
id="welcome-hint"
target="#welcome-button"
content="Click here to get started!"
render={({
isOpen,
targetRect,
targetElement,
show,
hide,
dismiss,
hotspotRef,
position,
tooltipPlacement,
content,
}) => (
<>
{/* Custom hotspot */}
<button
ref={hotspotRef}
onClick={isOpen ? hide : show}
style={{
position: 'fixed',
top: targetRect.top - 4,
left: targetRect.right - 4,
}}
className="w-4 h-4 bg-blue-500 rounded-full"
/>
{/* Custom tooltip */}
{isOpen && (
<div
style={{
position: 'fixed',
top: targetRect.bottom + 8,
left: targetRect.left,
}}
className="p-3 bg-white shadow-lg rounded"
>
<p>{content}</p>
<button onClick={dismiss}>Dismiss</button>
</div>
)}
</>
)}
/>
);
}Props
Prop
Type
Render Props
The render function receives these props:
Prop
Type
Position Options
// HotspotPosition values
type HotspotPosition =
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right'
| 'center';<HintHeadless
id="hint"
target="#element"
position="bottom-right" // Hotspot at bottom-right corner
render={({ targetRect, position }) => {
// position = 'bottom-right'
// targetRect = { top, left, right, bottom, width, height }
}}
/>Complete Custom Hint
import { HintHeadless } from '@tour-kit/hints/headless';
function FeatureHint() {
return (
<HintHeadless
id="new-export"
target="#export-button"
content="Export to multiple formats now available!"
position="top-right"
tooltipPlacement="right"
persist={true}
onDismiss={() => analytics.track('hint_dismissed')}
render={({
isOpen,
targetRect,
targetElement,
show,
hide,
dismiss,
content,
}) => (
<>
{/* Custom pulsing hotspot */}
<button
onClick={isOpen ? hide : show}
className="fixed z-50"
style={{
top: targetRect.top - 4,
left: targetRect.right - 4,
}}
>
<span className="flex h-4 w-4">
<span className="animate-ping absolute h-4 w-4 rounded-full bg-blue-400 opacity-75" />
<span className="relative rounded-full h-4 w-4 bg-blue-500" />
</span>
</button>
{/* Custom tooltip */}
{isOpen && (
<div
className="fixed z-50 p-4 bg-white rounded-lg shadow-xl"
style={{
top: targetRect.top,
left: targetRect.right + 12,
}}
>
<div className="flex items-start gap-2">
<span className="text-xl">🆕</span>
<div>
<p className="font-medium">{content}</p>
<div className="mt-2 flex gap-2">
<button
onClick={dismiss}
className="text-sm text-blue-600"
>
Got it
</button>
<button
onClick={hide}
className="text-sm text-gray-500"
>
Later
</button>
</div>
</div>
</div>
</div>
)}
</>
)}
/>
);
}Without Render Prop
For simple cases, use children instead:
<HintHeadless
id="simple-hint"
target="#button"
className="w-4 h-4 bg-blue-500 rounded-full"
>
{/* This renders when isOpen is true */}
<div className="tooltip">
<p>Hint content here</p>
</div>
</HintHeadless>When not using render, the component renders a basic button that toggles the tooltip. Use this for prototyping before implementing full custom rendering.
Related
- HintHotspotHeadless - Positioned hotspot
- HintTooltipHeadless - Floating tooltip
- useHint - Direct hook access