TourKit
@tour-kit/hintsHeadless

HintTooltipHeadless

HeadlessHintTooltip: unstyled floating tooltip with calculated position and collision avoidance via render props

HintTooltipHeadless

A headless floating tooltip component powered by Floating UI. Handles positioning, collision detection, and portal rendering.

Why Use This Component?

  • Smart positioning - Automatic flip and shift to avoid viewport edges
  • Portal rendering - Renders outside DOM hierarchy for proper stacking
  • Floating UI powered - Industry-standard positioning logic
  • Dismiss handling - Built-in click-outside and escape key support

Basic Usage

import { HintTooltipHeadless } from '@tour-kit/hints/headless';

function CustomTooltip() {
  const targetElement = document.getElementById('target');

  return (
    <HintTooltipHeadless
      target={targetElement}
      placement="bottom"
      onClose={() => setIsOpen(false)}
    >
      <div className="p-4 bg-white rounded shadow-lg">
        <p>Tooltip content here</p>
        <button onClick={() => setIsOpen(false)}>Close</button>
      </div>
    </HintTooltipHeadless>
  );
}

Props

Prop

Type

Also accepts all <div> props.


Render Props

Prop

Type


With Render Prop

<HintTooltipHeadless
  target={targetElement}
  placement="right"
  onClose={closeTooltip}
  render={({ floatingStyles, refs, getFloatingProps }) => (
    <div
      ref={refs.setFloating}
      style={floatingStyles}
      className="bg-gray-900 text-white p-4 rounded-lg shadow-xl"
      {...getFloatingProps()}
    >
      <h4 className="font-bold">New Feature!</h4>
      <p className="text-gray-300 mt-1">
        Check out our new export functionality.
      </p>
      <button
        onClick={closeTooltip}
        className="mt-2 text-blue-400 hover:underline"
      >
        Got it
      </button>
    </div>
  )}
/>

Placement Options

type Placement =
  | 'top' | 'top-start' | 'top-end'
  | 'bottom' | 'bottom-start' | 'bottom-end'
  | 'left' | 'left-start' | 'left-end'
  | 'right' | 'right-start' | 'right-end';

The tooltip automatically flips to the opposite side if there isn't enough space:

// Prefers bottom, but will flip to top if needed
<HintTooltipHeadless placement="bottom" />

Positioning Behavior

The component uses Floating UI middleware:

MiddlewareBehavior
offset(8)8px gap between target and tooltip
flip()Flips to opposite side if overflowing
shift({ padding: 8 })Shifts along axis to stay in viewport
autoUpdateUpdates position on scroll/resize

Portal Rendering

The tooltip renders in a portal at the document root:

// Internally uses FloatingPortal from @floating-ui/react
<FloatingPortal>
  <div style={floatingStyles} {...getFloatingProps()}>
    {children}
  </div>
</FloatingPortal>

Portal rendering ensures the tooltip appears above all other content regardless of parent z-index.


Dismiss Behavior

Built-in dismiss handling via Floating UI:

  • Click outside - Calls onClose when clicking outside tooltip
  • Escape key - Calls onClose when pressing Escape
  • Focus management - Tooltip has proper ARIA role
// These are handled automatically
<HintTooltipHeadless
  onClose={() => {
    // Called on:
    // - Click outside
    // - Escape key press
  }}
/>

Custom Arrow

<HintTooltipHeadless
  target={element}
  placement="bottom"
  onClose={close}
  render={({ floatingStyles, refs, getFloatingProps }) => (
    <div
      ref={refs.setFloating}
      style={floatingStyles}
      {...getFloatingProps()}
      className="relative"
    >
      {/* Arrow */}
      <div
        className="absolute -top-2 left-1/2 -translate-x-1/2
                   border-8 border-transparent border-b-white"
      />

      {/* Content */}
      <div className="bg-white p-4 rounded shadow-lg">
        <p>Tooltip with arrow!</p>
      </div>
    </div>
  )}
/>

Ref Forwarding

const tooltipRef = useRef<HTMLDivElement>(null);

<HintTooltipHeadless
  ref={tooltipRef}
  target={element}
  onClose={close}
>
  <p>Content</p>
</HintTooltipHeadless>

Complete Example

import { HintTooltipHeadless } from '@tour-kit/hints/headless';
import { XIcon } from 'lucide-react';

function FeatureTooltip({ target, onClose }) {
  return (
    <HintTooltipHeadless
      target={target}
      placement="right-start"
      onClose={onClose}
      render={({ floatingStyles, refs, getFloatingProps }) => (
        <div
          ref={refs.setFloating}
          style={floatingStyles}
          className="w-72 bg-gradient-to-r from-blue-500 to-purple-500 p-4 rounded-xl shadow-2xl text-white"
          {...getFloatingProps()}
        >
          <div className="flex justify-between items-start">
            <div className="flex items-center gap-2">
              <span className="text-2xl">✨</span>
              <h4 className="font-bold">New Feature</h4>
            </div>
            <button
              onClick={onClose}
              className="p-1 hover:bg-white/20 rounded"
            >
              <XIcon className="w-4 h-4" />
            </button>
          </div>

          <p className="mt-2 text-white/90">
            Try our new export feature to download your data
            in multiple formats.
          </p>

          <div className="mt-3 flex gap-2">
            <button
              onClick={onClose}
              className="flex-1 py-2 bg-white text-blue-600 rounded-lg font-medium"
            >
              Try it now
            </button>
            <button
              onClick={onClose}
              className="px-3 py-2 bg-white/20 rounded-lg"
            >
              Later
            </button>
          </div>
        </div>
      )}
    />
  );
}

On this page