FSAI Design System
Components

Tooltip

Floating hint and metadata surface for hover, focus, or click-triggered disclosure.

Overview

Tooltip is the shared floating hint primitive used across @fsai/shared-ui.

It is built on top of Floating UI and exported as a small compound API:

  • Tooltip
  • TooltipTrigger
  • TooltipContent
  • useTooltip

Use it for short contextual help, labels for icon-only controls, compact metadata previews, and conditional disabled-state explanations.

Basic Usage

import { Tooltip, TooltipContent, TooltipTrigger } from '@fsai/shared-ui';

<Tooltip>
  <TooltipTrigger asChild>
    <button type="button">Hover me</button>
  </TooltipTrigger>
  <TooltipContent>Helpful context</TooltipContent>
</Tooltip>

By default, the tooltip opens on hover and focus and closes on dismiss.

Trigger Composition

Use TooltipTrigger asChild when the trigger should be an existing element such as a button, link, badge, chart element, or custom control.

<Tooltip placement="right">
  <TooltipTrigger asChild>
    <Badge color="gray" label="Primary" className="cursor-pointer" />
  </TooltipTrigger>
  <TooltipContent>#3B82F6</TooltipContent>
</Tooltip>

If you do not use asChild, TooltipTrigger renders its own button.

Click-Triggered Help Tooltip

For inline help affordances, use triggerOnClick.

<Tooltip triggerOnClick>
  <TooltipTrigger
    className="inline-flex h-5 w-5 items-center justify-center rounded-full"
    aria-label="Show more information"
  >
    <CircleInfo className="h-4 w-4" />
  </TooltipTrigger>
  <TooltipContent className="max-w-xs text-md leading-5">
    Extra guidance for this field
  </TooltipContent>
</Tooltip>

This is the pattern used in GuidedFlow for optional explanatory help.

Controlled Tooltip State

Use controlled open when the tooltip should reflect component state instead of normal hover behavior.

<Tooltip open={isDragging && showValueInTooltip ? true : false}>
  <TooltipTrigger asChild>
    <div onMouseDown={startDragging} className="slider-handle" />
  </TooltipTrigger>
  {showValueInTooltip && (
    <TooltipContent>{formatValue(renderedValue)}</TooltipContent>
  )}
</Tooltip>

This is the pattern used in RangeSlider to show the live value while dragging.

Conditional Or Disabled Tooltips

When tooltip content is optional, disable the tooltip entirely instead of rendering an empty content shell.

<Tooltip disabled={!tooltipText}>
  <TooltipTrigger asChild>
    <button type="button" aria-label="More actions">
      <MoreHorizontal />
    </button>
  </TooltipTrigger>
  {tooltipText && <TooltipContent>{tooltipText}</TooltipContent>}
</Tooltip>

This is the same pattern used in components like ModifiersBarButton.

Rich Content Tooltip

TooltipContent can hold more than a plain string when the content is still lightweight and glanceable.

<Tooltip placement="right">
  <TooltipTrigger asChild>
    <g>{/* chart region */}</g>
  </TooltipTrigger>
  <TooltipContent className="p-2 w-36 flex flex-col items-start">
    <div className="p-1 bg-gray-bg-strong rounded-md mb-2">California</div>
    <p className="text-md font-semibold text-strong">{formattedValue}</p>
  </TooltipContent>
</Tooltip>

This is how GeoChart renders compact region metadata.

Props

Tooltip

PropTypeDefaultDescription
childrenReactNodeRequired. Usually TooltipTrigger and TooltipContent.
placementPlacement'top'Floating placement.
initialOpenbooleanfalseInitial uncontrolled open state.
openbooleanControlled open state.
onOpenChange(open: boolean) => voidControlled open-state callback.
disabledbooleanfalsePrevents the tooltip from opening.
triggerOnClickbooleanfalseAdds click-trigger behavior for the trigger.

TooltipTrigger

PropTypeDefaultDescription
asChildbooleanfalseReuses the child element as the trigger instead of rendering a wrapper button.
childrenReactNodeTrigger content or the child element when asChild is used.
...htmlPropsHTMLProps<HTMLElement>Standard trigger element props.

Behavior notes:

  • when asChild is used, existing child handlers are merged with tooltip handlers
  • the trigger receives data-state="open" or data-state="closed"
  • without asChild, the trigger is rendered as a button

TooltipContent

PropTypeDefaultDescription
childrenReactNodeTooltip body content.
classNamestringAdditional content classes.
arrowClassNamestringAdditional classes for the tooltip arrow.
...htmlPropsHTMLProps<HTMLDivElement>Standard floating content props.

useTooltip

useTooltip is the low-level hook behind the compound components.

Use it only when you need a custom tooltip composition that cannot be expressed with Tooltip, TooltipTrigger, and TooltipContent directly. Most product code should use the standard components instead.

Common Usage Patterns

PatternPathNotes
Disabled-action explanationfsai/packages/shared-ui/src/primitives/Menu/Menu.primitives.tsxMenu items use tooltips for disabled-item context.
Primitive-powered avatar labelsfsai/packages/shared-ui/src/primitives/Avatar/Avatar.tsxAvatar composes Tooltip internally for optional name disclosure.
Click-triggered field helpfsai/packages/shared-ui/src/components/GuidedFlow/GuidedFlow.tsxInfo icon opens a help tooltip on click.
Controlled dragging valuefsai/packages/shared-ui/src/components/RangeSlider/RangeSlider.tsxTooltip visibility is driven by drag state.
Optional sidebar labelfsai/packages/shared-ui/src/components/DashboardSidebar/DashboardSidebar.tsxExisting nodes are wrapped with TooltipTrigger asChild.
Rich chart hover cardfsai/packages/shared-ui/src/components/Charts/GeoChart.tsxTooltip content contains structured metadata, not just a string.

Important Conventions

  • Prefer TooltipTrigger asChild when the trigger is already a real interactive or semantic element.
  • Keep tooltip content short and glanceable. If the content becomes workflow-level or action-heavy, use another pattern.
  • Use triggerOnClick for help or info affordances that should be deliberate rather than hover-only.
  • Disable the tooltip when there is no content instead of rendering an empty TooltipContent.
  • The trigger gets data-state, so open styling can be driven from CSS utilities.
  • TooltipContent is portaled and only renders while open.
  • Tooltip is not a replacement for a persistent label or accessible field description.

Guidelines

  • Do use tooltips for concise supplemental context
  • Do use asChild for existing buttons, links, badges, and custom nodes
  • Do control open when the tooltip should follow component state
  • Do keep the content lightweight even when it includes structured markup
  • Don't use a tooltip as the only way to understand a core workflow action
  • Don't render empty or meaningless tooltip shells
  • Don't use tooltip content as a substitute for accessible labels on icon-only controls

On this page