Skip to main content
On this pageOverview

Tooltip

Overview

A non-interactive floating label anchored to a trigger. Tooltips appear on hover after a short delay, or immediately on keyboard focus. They hide on pointer-leave, blur, Escape, or left-click of the trigger. Use tooltips for short hints about a control. For rich content or interactive panels, use Popover instead.

The positioning engine is shared with Popover and Menu — pass anchor to control placement and spacing.

See it in an app

Check out how Tooltip is wired up in a real Foldkit app.

Examples

Hover or tab into the trigger to reveal the tooltip. Hover waits for showDelay (default 500ms); keyboard focus shows it immediately.

// Pseudocode walkthrough of the Foldkit integration points. Each labeled
// block below is an excerpt — fit them into your own Model, init, Message,
// update, and view definitions.
import { Effect } from 'effect'
import { Command, Ui } from 'foldkit'
import { m } from 'foldkit/message'
import { evo } from 'foldkit/struct'

import { Class, span } from './html'

// Add a field to your Model for the Tooltip Submodel:
const Model = S.Struct({
  tooltip: Ui.Tooltip.Model,
  // ...your other fields
})

// In your init function, initialize the Tooltip Submodel with a unique id:
const init = () => [
  {
    tooltip: Ui.Tooltip.init({ id: 'save-button' }),
    // ...your other fields
  },
  [],
]

// Embed the Tooltip Message in your parent Message:
const GotTooltipMessage = m('GotTooltipMessage', {
  message: Ui.Tooltip.Message,
})

// Inside your update function's M.tagsExhaustive({...}), delegate to Tooltip.update:
GotTooltipMessage: ({ message }) => {
  const [nextTooltip, commands] = Ui.Tooltip.update(model.tooltip, message)

  return [
    // Merge the next state into your Model:
    evo(model, { tooltip: () => nextTooltip }),
    // Forward the Submodel's Commands through your parent Message:
    commands.map(
      Command.mapEffect(Effect.map(message => GotTooltipMessage({ message }))),
    ),
  ]
}

// Inside your view function, render the tooltip:
Ui.Tooltip.view({
  model: model.tooltip,
  toParentMessage: message => GotTooltipMessage({ message }),
  anchor: { placement: 'top', gap: 6, padding: 8 },
  triggerContent: span([], ['Save']),
  triggerClassName: 'rounded-lg border px-3 py-2 cursor-pointer',
  content: span([], ['Save your changes (⌘S)']),
  panelClassName:
    'rounded-md bg-gray-900 px-3 py-1.5 text-sm text-white shadow-lg',
})

Styling

Tooltip is headless — the trigger and panel are both styled through className and attribute props. The panel is rendered with pointer-events: none so it never captures hover or clicks, which keeps the open/close logic tied to the trigger.

AttributeCondition
data-openPresent on trigger and panel when the tooltip is visible.
data-disabledPresent on the trigger when disabled.

Keyboard Interaction

KeyDescription
EscapeHides the tooltip while visible. It will not reopen until the user disengages by moving the pointer away or blurring the trigger.

Accessibility

The panel has role="tooltip" and the trigger is linked via aria-describedby. Focus is never moved into the tooltip, so assistive technology announces the panel contents as a description of the trigger.

API Reference

InitConfig

Configuration object passed to Tooltip.init().

NameTypeDefaultDescription
idstring-Unique ID for the tooltip instance.
showDelayDuration.DurationInputDuration.millis(500)How long the pointer must hover before the tooltip appears. Accepts any Effect Duration input — a bare number is interpreted as milliseconds. Keyboard focus shows the tooltip immediately regardless of this value.

ViewConfig

Configuration object passed to Tooltip.view().

NameTypeDefaultDescription
modelTooltip.Model-The tooltip state from your parent Model.
toParentMessage(childMessage: Tooltip.Message) => ParentMessage-Wraps Tooltip Messages in your parent Message type for Submodel delegation.
anchorAnchorConfig-Floating positioning config: placement, gap, and padding. Required.
triggerContentHtml-Content rendered inside the trigger button.
contentHtml-Content rendered inside the tooltip panel.
triggerClassNamestring-CSS class for the trigger button.
triggerAttributesReadonlyArray<Attribute<Message>>-Additional attributes for the trigger button.
panelClassNamestring-CSS class for the panel.
panelAttributesReadonlyArray<Attribute<Message>>-Additional attributes for the panel.
isDisabledbooleanfalseDisables the trigger. Hover, focus, and keyboard events are ignored and the tooltip will not open.

Programmatic Helpers

Helper functions for driving the tooltip from parent update handlers, returning [Model, Commands].

NameTypeDefaultDescription
setShowDelay(model: Model, showDelay: Duration.DurationInput) => [Model, Commands]-Updates the hover show-delay. Accepts any Effect Duration input (a bare number is interpreted as milliseconds). Applies to subsequent hovers; any in-flight delay is invalidated.

Stay in the update loop.

New releases, patterns, and the occasional deep dive.


Built with Foldkit.

© 2026 Devin Jameson