Tooltip
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.
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> );}// Android: Jetpack Compose, Material 3 (BOM 2026.02.01). The plain tooltip is the// intended path: a high-contrast PlainTooltip in a TooltipBox, shown on long-press// (touch) and on hover/focus (pointer + keyboard). NOTE: not yet adopted in-app;// icon-only actions name themselves via `contentDescription` today (see drift).import androidx.compose.material3.PlainTooltipimport androidx.compose.material3.TooltipBoximport androidx.compose.material3.TooltipDefaultsimport androidx.compose.material3.rememberTooltipState
@OptIn(ExperimentalMaterial3Api::class)@Composablefun CopyAction(onClick: -> Unit) { TooltipBox( positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), tooltip = { PlainTooltip { Text("Copy link") } }, state = rememberTooltipState(), ) { NockerlIconButton( icon = Icons.Filled.ContentCopy, contentDescription = "Copy link", onClick = onClick, ) }}// macOS: SwiftUI (canonical). The native `.help(_:)` modifier IS the macOS// tooltip: a brief hint shown on hover after the system delay (~2s), no styling// hooks. Applied to icon-only buttons and form controls, where the same string// also serves as the accessibility label. (For an instant, themed panel, use InfoTip.)Button(action: copyLink) { Image(systemName: "doc.on.doc")}.buttonStyle(.borderless).help("Copy link")
// On the Nockerl icon button it is a first-class parameter:NockerlIconButton(systemName: "square.and.arrow.up", help: "Share session") { share()}
// Any control takes it, and a longer hint wraps automatically:Toggle("Input Monitoring", isOn: $monitoring) .help("Needed for Input Monitoring to take effect.")Parameters
Section titled “Parameters”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.
| Prop | Type | Default | Description |
|---|---|---|---|
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. |
delayMs | number | 600 | Open delay in ms after hover/focus before a tip floats in. Pass 0 for an immediate tip. |
children * | (args: TooltipRenderArgs) => ReactNode | Render-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) | Type | Default | Description |
|---|---|---|---|
key * | string | Identity of the trigger that owns the open tip (so exactly one tip shows at a time). | |
text * | string | The 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 * | boolean | Allow wrapping for a longer tip (otherwise a single line). | |
width * | number | Max width hint (px) for the bubble, so it stays compact. |
PlainTooltip is shown inside a TooltipBox; the trigger is its content. The
position provider, the state (delay/persistence), and the surface colors come from
TooltipDefaults; there is no single tooltip parameter.
| Parameter | Type | Default | Description |
|---|---|---|---|
positionProvider * | PopupPositionProvider | Where the tip anchors. Use TooltipDefaults.rememberPlainTooltipPositionProvider() (above the anchor, flips/clamps to the window). | |
tooltip * | @Composable TooltipScope.() -> Unit | The tip content: a PlainTooltip { Text(…) } for a brief hint. Keep it text only. | |
state * | TooltipState | From rememberTooltipState(). Drives show/dismiss; isPersistent = false auto-dismisses on the next interaction. | |
enableUserInput | Boolean | true | Whether long-press / hover gestures trigger the tip (vs. showing it programmatically only). |
caretSize | DpSize | TooltipDefaults.caretSize | The arrow/beak size on PlainTooltip. DpSize.Unspecified hides the beak. |
containerColor | Color | inverseSurface | Tip surface on PlainTooltip, high-contrast/inverted (the Nockerl theme maps it to onCanvas); content uses inverseOnSurface → canvas. |
The macOS tooltip is the native .help(_:) view modifier, a single string. The
~2s open delay, the dark high-contrast surface, and positioning are all
system-owned; there are no styling or timing parameters.
| Style | Type | Default | Description |
|---|---|---|---|
.help(_:) | View modifier | The hint text (a Text / LocalizedStringKey). Shown on hover after the system delay; also read by VoiceOver. The only knob. | |
help (param) | String | On NockerlIconButton, a first-class help parameter forwarded to .help(_:) to name the icon-only action. |