macbetter

Presentation

Tooltip

Name a control or add a short hint when the pointer rests on it.

macOS only
"use client"

import { Button } from "macbetter/button"
import { Tooltip, TooltipContent, TooltipShortcut, TooltipTrigger } from "macbetter/tooltip"
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react"

const styles = [
  { label: "Bold", shortcut: "mod+b", Icon: BoldIcon },
  { label: "Italic", shortcut: "mod+i", Icon: ItalicIcon },
  { label: "Underline", shortcut: "mod+u", Icon: UnderlineIcon },
]

export default function Example() {
  return (
    <div style={{ display: "flex", gap: 4 }}>
      {styles.map(({ label, shortcut, Icon }) => (
        <Tooltip key={label} shortcut={shortcut}>
          <TooltipTrigger render={<Button variant="plain" tone="neutral" shape="circle" aria-label={label} />}>
            <Icon aria-hidden="true" />
          </TooltipTrigger>
          <TooltipContent>
            {label}
            <TooltipShortcut />
          </TooltipContent>
        </Tooltip>
      ))}
    </div>
  )
}

When to use it

On the Mac, a help tag names an icon-only button or says briefly what a control does. Keep it to a few words, and describe the action, not the icon (“Bold”, not “B button”). Never put anything in a tooltip that people need: it only appears for someone who pauses with a pointer.

Touch screens have no hover, so on iPhone and iPad tooltips don’t appear at all. Label controls there with text, or use a Popover for a longer explanation.

Usage

import { Tooltip, TooltipContent, TooltipShortcut, TooltipTrigger } from "macbetter/tooltip"

The help tag waits 400ms before it appears and appears at once on keyboard focus. Set delay for a longer pause, such as the Mac's own 800ms, or a shorter one. Once one is showing, moving to the next control shows its tag straight away, without the delay or the fade, so moving along a toolbar reads as one tag following the pointer.

Keyboard shortcuts

Give the root a shortcut and put TooltipShortcut in the content to show it, formatted for the platform (⌘B on Apple devices).

Info buttons on touch

For a button whose only job is the hint, toggleable opens and closes the tooltip on press, so it works with a finger too.

Storage12.4 of 15 GB used
"use client"

import { Button } from "macbetter/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "macbetter/tooltip"
import { InfoIcon } from "lucide-react"

export default function Example() {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
      <span>Storage</span>
      <Tooltip toggleable>
        <TooltipTrigger render={<Button variant="plain" size="sm" shape="circle" aria-label="About storage" />}>
          <InfoIcon aria-hidden="true" />
        </TooltipTrigger>
        <TooltipContent>Files in the Trash still use storage.</TooltipContent>
      </Tooltip>
      <span style={{ color: "var(--mb-label-secondary)" }}>12.4 of 15 GB used</span>
    </div>
  )
}

Platform behavior

  • macOS. A small rectangle of thick material with 6pt corners and 11pt text, below the control, with no arrow. It fades in quickly and out faster.
  • iOS and iPadOS. No hover, so a plain tooltip never opens. A toggleable one opens on tap with 13pt text.

Accessibility

  • The tooltip names a trigger that has no name of its own, like an icon button, and describes any other. Set type to choose.
  • It opens when keyboard focus reaches the trigger and Escape closes it without moving focus.
  • It can’t be focused, so never put links or buttons in it.

API

Tooltip

Takes every ui-sdk Tooltip root prop.

PropTypeDefaultDescription
delaynumber400Milliseconds the pointer rests before it opens.
closeDelaynumber0Milliseconds it stays after the pointer leaves.
shortcutstring—The trigger's keyboard shortcut, like mod+b, shown by TooltipShortcut.
toggleablebooleanfalsePressing the trigger opens and closes it, for touch.
type"label" | "description"—Whether it names or describes its trigger.

TooltipContent

PropTypeDefaultDescription
side"top" | "right" | "bottom" | "left""bottom"Below the control, as macOS places help tags.
sideOffsetnumber6The gap to the trigger.
collisionPaddingnumber8Space kept from the window edges.

TooltipTrigger and TooltipShortcut take ui-sdk’s props. createTooltipController is re-exported.

Data attributeOnMeaning
data-side, data-align.mb-tooltipWhere it landed
data-instant.mb-tooltipOpened at once after another tag; skips the fade
data-starting-style, data-ending-style.mb-tooltipEntering and leaving
CSS variableDefault
--mb-tooltip-font11px on macOS, 13px on touch
--mb-tooltip-padding3px 7px on macOS