FairGarden Design

Tooltip

A Base UI Tooltip that names an icon-only control or gives a brief, non-essential hint, on the overlay scope's face.

import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from '@fairgarden-private/design/components/Tooltip'

It opens on hover after 500 ms (--ds-delay-tooltip) and on keyboard focus, appears instantly with no fade, stays open while the pointer moves onto it, and Esc dismisses it. The popup is one short line of caption text in a --border-size-2 frame, at most 280 px wide. Touch screens never show it, and it never prints, so never put links, actions or required information in a tooltip.

Label, hint and term

Label, hint and term

kind="label" (default) wraps your control through render; its text equals the control's accessible name. kind="hint" draws the Material help glyph beside a label, with the children as its hidden name. kind="term" underlines a term in line-dotted-fine; its definition must also exist as a footnote. A TooltipProvider lets neighbors open at once.

Membership tier

The parcel is protected by a held by the trust.

TooltipVariants.tsx
import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from '@fairgarden-private/design/components/Tooltip'
import { Button } from '@fairgarden-private/design/components/Button'
import styles from './variants.module.css'

/**
 * The three trigger forms: a label on icon-only buttons (sharing one
 * Provider, so neighbors open at once), a `help` hint beside a label, and a
 * dotted-underline term in running text.
 */
export function TooltipVariants() {
  return (
    <div className={styles.stack}>
      <TooltipProvider>
        <div className={styles.row}>
          <Tooltip>
            <TooltipTrigger render={<Button iconOnly icon="zoom_in">Zoom In</Button>} />
            <TooltipPopup>Zoom In</TooltipPopup>
          </Tooltip>
          <Tooltip>
            <TooltipTrigger render={<Button iconOnly icon="zoom_out">Zoom Out</Button>} />
            <TooltipPopup>Zoom Out</TooltipPopup>
          </Tooltip>
          <Tooltip>
            <TooltipTrigger render={<Button iconOnly icon="recenter">Reset View</Button>} />
            <TooltipPopup>Reset View</TooltipPopup>
          </Tooltip>
        </div>
      </TooltipProvider>

      <p className={styles.copy}>
        Membership tier{' '}
        <Tooltip>
          <TooltipTrigger kind="hint">About membership tiers</TooltipTrigger>
          <TooltipPopup>Tiers renew each spring.</TooltipPopup>
        </Tooltip>
      </p>

      <p className={styles.copy}>
        The parcel is protected by a{' '}
        <Tooltip>
          <TooltipTrigger kind="term">conservation easement</TooltipTrigger>
          <TooltipPopup side="bottom">A legal limit on development that stays with the deed.</TooltipPopup>
        </Tooltip>{' '}
        held by the trust.
      </p>
    </div>
  )
}

Primary scale

Primary scale

The popup is a portaled overlay scope: it takes only the props passed to TooltipPopup and never inherits the trigger's scales.

TooltipColor.tsx
import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from '@fairgarden-private/design/components/Tooltip'
import { Button } from '@fairgarden-private/design/components/Button'
import styles from './color.module.css'

/**
 * The popup takes only the props passed to it: `primary` recolors its frame
 * and text, never the trigger's scales.
 */
export function TooltipColor() {
  return (
    <TooltipProvider>
      <div className={styles.row}>
        <Tooltip>
          <TooltipTrigger render={<Button iconOnly icon="download">Download Map</Button>} />
          <TooltipPopup>Overlay default</TooltipPopup>
        </Tooltip>
        <Tooltip>
          <TooltipTrigger render={<Button iconOnly icon="download">Download Map</Button>} />
          <TooltipPopup primary="plum">Primary plum</TooltipPopup>
        </Tooltip>
      </div>
    </TooltipProvider>
  )
}

On grounds

On paper and forest

paper

Trail grade

A sits at the center.

forest

Trail grade

A sits at the center.

TooltipGrounds.tsx
import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from '@fairgarden-private/design/components/Tooltip'
import { PresetGround } from '@/components/PresetGround'
import styles from './grounds.module.css'

const presets = ['paper', 'forest'] as const

/** The hint glyph and term underline follow the trigger's ground; the popup face is always the overlay scope. */
export function TooltipGrounds() {
  return (
    <div className={styles.row}>
      {presets.map((preset) => (
        <PresetGround key={preset} preset={preset} className={styles.face}>
          <p className={styles.name}>{preset}</p>
          <p className={styles.copy}>
            Trail grade{' '}
            <Tooltip>
              <TooltipTrigger kind="hint">About trail grades</TooltipTrigger>
              <TooltipPopup>Grades run from easy to strenuous.</TooltipPopup>
            </Tooltip>
          </p>
          <p className={styles.copy}>
            A{' '}
            <Tooltip>
              <TooltipTrigger kind="term">kettle pond</TooltipTrigger>
              <TooltipPopup>A pond left by a melting glacier.</TooltipPopup>
            </Tooltip>{' '}
            sits at the center.
          </p>
        </PresetGround>
      ))}
    </div>
  )
}

API Reference

TooltipProvider and Tooltip

Shares the open delay across a group of triggers, such as a toolbar: the first tooltip opens after --ds-delay-tooltip (500 ms), its neighbors open at once while one is showing.

One tooltip: a TooltipTrigger and a TooltipPopup. It opens on hover after 500 ms and on keyboard focus, appears instantly, stays open while the pointer moves onto it, and Esc dismisses it without moving focus. Touch screens never show it, so the trigger must make sense without it. It never prints: definitions behind terms must also exist as footnotes.

TooltipTrigger

The element the tooltip names. With kind="label", pass the control as render, e.g. render={<Button iconOnly icon="search">Search</Button>}. The hint glyph and the term underline follow the trigger’s own ground.

PropTypeDescription
kind
TooltipTriggerKind | undefined

label (default): the trigger is your control, passed through render (an icon-only Button); the popup text equals its accessible name. hint: a Material help glyph at the inline tier beside a label; the children become its visually hidden accessible name. term: the children, underlined in line-dotted-fine at a 2 px offset.

TooltipPopup

The popup: one short line of type-caption (three at most), at most --ds-size-tooltip (280 px) wide, on the overlay scope’s --primary1 face inside a --border-size-2 --primary12 frame with --radius-1. It sits --size-px-2 from the trigger (side, default top) and flips or shifts to stay inside the viewport.

PropTypeDescription
portalProps
Omit<TooltipPortalProps, 'children'> | undefined

Props for the Base UI Portal, such as container.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale inside the popup’s scope: face, edge and text. Omitted, the overlay scope’s default applies; the popup never inherits the trigger’s scales.

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale inside the popup’s scope; no part uses it.

Additional types

tooltip
TOOLTIP_DELAY_MS
type TOOLTIP_DELAY_MS = 500
TooltipPopupProps

Props for TooltipPopup: Base UI Tooltip Popup props plus placement and the color axes.

type TooltipPopupProps = TooltipPopupProps &
  Pick<TooltipPositionerProps, 'side' | 'align'> & {
    primary?:
      | 'ruby'
      | 'olive'
      | 'sage'
      | 'slate'
      | 'sand'
      | 'gray'
      | 'mauve'
      | 'brown'
      | 'bronze'
      | 'gold'
      | 'red'
      | 'crimson'
      | 'tomato'
      | 'pink'
      | 'plum'
      | 'indigo'
      | 'iris'
      | 'violet'
      | 'purple'
      | null;
    secondary?:
      | 'ruby'
      | 'olive'
      | 'sage'
      | 'slate'
      | 'sand'
      | 'gray'
      | 'mauve'
      | 'brown'
      | 'bronze'
      | 'gold'
      | 'red'
      | 'crimson'
      | 'tomato'
      | 'pink'
      | 'plum'
      | 'indigo'
      | 'iris'
      | 'violet'
      | 'purple'
      | 'amber'
      | 'blue'
      | 'cyan'
      | 'grass'
      | 'green'
      | 'jade'
      | 'lime'
      | 'mint'
      | 'orange'
      | 'sky'
      | 'teal'
      | 'yellow'
      | null;
    portalProps?: Omit<TooltipPortalProps, 'children'>;
  }
TooltipProps

Props for Tooltip: Base UI Tooltip Root props.

type TooltipProps<Payload = unknown> = TooltipRoot.Props<Payload>
TooltipProviderProps

Props for TooltipProvider: Base UI Tooltip Provider props.

type TooltipProviderProps = TooltipProvider.TooltipProviderProps
tooltipTrigger
TooltipTriggerKind
type TooltipTriggerKind = 'label' | 'hint' | 'term'<'label' | 'hint' | 'term' | null | undefined>

Specification: DESIGN-SYSTEM.md §10.16 (tooltip) and §10.1 (the overlay surface).