Skip to content

Tooltip

In review
Live · web

Hover or Tab to a target: a brief hint floats in after a short delay

The tip shows on hover or keyboard focus (Tab through the row), points at the control with a beak, and dismisses on leave, blur, or Esc. It is non-interactive: you cannot click into it.

NockerlIcon-only buttons
Longer tip
Inline definition
Immediate
Disabled (wrapped)

Tip shown 0 times · placement auto · ~600ms delay · hover + keyboard both work; the island is live.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform;
// this is the canonical API the published @dizyx/nockerl-react package exposes.)
// NockerlTooltip is HEADLESS: it owns the open delay + the anchor → flip → clamp → beak
// engine, and hands a render-prop back {{ stageRef, triggerProps, tip }}. You mount
// your OWN triggers inside a contained stage (stageRef) and wire each via
// triggerProps(cfg); the tip floats as the LAST child INSIDE the stage. Each hint is
// non-interactive (text only); for a panel you tab into, use NockerlPopover instead.
import { NockerlTooltip, NockerlIconButton, NockerlIcon } from '@dizyx/nockerl-react';
// Glyphs are inline SVG nodes (the NockerlIcon registry only carries a few named paths, so
// pass a bespoke `path`/children for the rest).
const CopyIcon = <NockerlIcon><path d="M9 9h11v11H9z" /><path d="M5 15V5a2 2 0 0 1 2-2h10" /></NockerlIcon>;
const StarIcon = <NockerlIcon path="M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.9 6.8 19.2l1-5.8L3.5 9.2l5.9-.9z" />;
const InfoIcon = <NockerlIcon><path d="M12 16v-4" /><path d="M12 8h.01" /><path d="M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z" /></NockerlIcon>;
const TrashIcon = <NockerlIcon><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><path d="M6 6v14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V6" /></NockerlIcon>;
export function SessionToolbar() {
return (
// `place` forces a side (or 'auto' = each trigger's own); `delayMs` is the open delay.
<NockerlTooltip place="auto" delayMs={600}>
{({ stageRef, triggerProps, tip }) => (
// The contained STAGE: every tip floats INSIDE here, clamped to it (never the page).
<div className="toolbar" ref={stageRef} style={{ position: 'relative' }}>
{/* NockerlIcon-only buttons are the canonical use. The tip NAMES the action. Each
trigger declares its own hint via triggerProps({{ key, text, side, ... }}). */}
<NockerlIconButton
icon={CopyIcon}
label="Copy link"
onClick={copyLink}
{...triggerProps({ key: 'copy', text: 'Copy link', side: 'bottom', multiline: false, width: 220 })}
/>
<NockerlIconButton
icon={StarIcon}
label="Add to favorites"
onClick={favorite}
{...triggerProps({ key: 'star', text: 'Add to favorites', side: 'bottom', multiline: false, width: 220 })}
/>
{/* A longer hint wraps (multiline); `side` is a preference, so it flips/clamps to fit. */}
<NockerlIconButton
icon={InfoIcon}
label="About context usage"
{...triggerProps({
key: 'info',
text: 'Context window usage across this session, including system prompt and tool results.',
side: 'left',
multiline: true,
width: 240,
})}
/>
{/* A disabled control fires no hover/focus; wire an enabled wrapper so the tip still shows. */}
<span
tabIndex={0}
role="button"
aria-disabled="true"
aria-label="Delete"
{...triggerProps({ key: 'del', text: 'Finish the running task before deleting', side: 'top', multiline: true, width: 220 })}
>
<NockerlIconButton icon={TrashIcon} label="Delete" disabled />
</span>
{/* The live tip: render it as the LAST child INSIDE the stage. */}
{tip}
</div>
)}
</NockerlTooltip>
);
}

Tooltip is headless: it owns the open delay + the anchor → flip → clamp → beak engine and returns a render-prop. The per-hint text / side / wrapping are declared on each trigger through triggerProps(cfg), not on Tooltip.

PropTypeDefaultDescription
place'auto' | 'top' | 'bottom' | 'left' | 'right''auto'Forced placement for every tip, or auto to use each trigger's own preferred side. A hint only: the tip flips to the opposite side and clamps inside the stage if it would not fit.
delayMsnumber600Open delay in ms after hover/focus before a tip floats in. Pass 0 for an immediate tip.
children *(args: TooltipRenderArgs) => ReactNodeRender-prop. Receives { stageRef, triggerProps, tip, shows }: mount your triggers inside the stageRef stage, wire each with triggerProps(cfg), and render tip as the stage's LAST child.

Each trigger declares its own hint via triggerProps(cfg: TooltipConfig):

triggerProps(cfg)TypeDefaultDescription
key *stringIdentity of the trigger that owns the open tip (so exactly one tip shows at a time).
text *stringThe hint. Plain text only, because a tooltip is non-interactive.
side *'top' | 'bottom' | 'left' | 'right'Preferred side for this trigger (used when Tooltip place="auto"). Flips / clamps to fit.
multiline *booleanAllow wrapping for a longer tip (otherwise a single line).
width *numberMax width hint (px) for the bubble, so it stays compact.