Monet
@etched/monet 0.0.1

Tooltip

Ported

A short label shown on hover and keyboard focus. Replaces the native title attribute. While it shows, it describes the trigger. It does not name the trigger: icon-only buttons still need aria-label. ref goes to the trigger.

Kumo source
import { DownloadSimpleIcon, PlusIcon } from "@phosphor-icons/react";
import { Button } from "@etched/monet/button";
import { Tooltip, TooltipProvider } from "@etched/monet/tooltip";

export function Preview() {
  return (
    <TooltipProvider>
      <div className="row">
        <Tooltip content="Add chip" defaultOpen render={<Button shape="square" aria-label="Add chip" icon={<PlusIcon />} />} />
        <Tooltip content="Download CSV" render={<Button shape="square" aria-label="Download CSV" icon={<DownloadSimpleIcon />} />} />
      </div>
    </TooltipProvider>
  );
}

Installation

import { Tooltip, TooltipProvider } from "@etched/monet/tooltip";

Examples

Icon buttons

The tooltip does not name the button: give it an aria-label too.

import { DownloadSimpleIcon, PlusIcon, TrashIcon } from "@phosphor-icons/react";
import { Button } from "@etched/monet/button";
import { Tooltip, TooltipProvider } from "@etched/monet/tooltip";

export function IconButtons() {
  return (
    <TooltipProvider>
      <div className="row">
        <Tooltip content="Add chip" render={<Button shape="square" aria-label="Add chip" icon={<PlusIcon />} />} />
        <Tooltip content="Download CSV" render={<Button shape="square" aria-label="Download CSV" icon={<DownloadSimpleIcon />} />} />
        <Tooltip
          content="Delete trace"
          render={<Button shape="square" variant="ghost" aria-label="Delete trace" icon={<TrashIcon />} />}
        />
      </div>
    </TooltipProvider>
  );
}

Text and plain triggers

Without render, the children go in a plain button.

import { InfoIcon } from "../../icons.js";
import { Button } from "@etched/monet/button";
import { Tooltip, TooltipProvider } from "@etched/monet/tooltip";

export function TextAndPlainTriggers() {
  return (
    <TooltipProvider>
      <div className="row">
        <Tooltip content="Runs every night at 02:00 PT." render={<Button />}>
          Schedule
        </Tooltip>
        <Tooltip content="Owners get alerts for their chips.">
          <InfoIcon />
          What is an owner?
        </Tooltip>
      </div>
    </TooltipProvider>
  );
}

Sides

import { Button } from "@etched/monet/button";
import { Tooltip, TooltipProvider, type TooltipSide } from "@etched/monet/tooltip";

const sides: TooltipSide[] = ["top", "right", "bottom", "left"];

export function Sides() {
  return (
    <TooltipProvider>
      <div className="row">
        {sides.map((side) => (
          <Tooltip key={side} content={`On the ${side}`} side={side} render={<Button variant="outline" />}>
            {side}
          </Tooltip>
        ))}
      </div>
    </TooltipProvider>
  );
}

Accessibility

  • Keyboard focus on the trigger opens it, as hover does. Escape closes it, and focus stays on the trigger.
  • The popup has the tooltip role. Without render, the children go in a plain native button.
  • While it shows, the trigger is described by it (aria-describedby). Touch users may not see it.
  • Give an icon-only trigger an aria-label that matches the tooltip text. Put needed information in text or a Popover, not only in a tooltip.

API reference

PropTypeDefaultDescription
positionMethodBaseTooltip.Positioner.Props["positionMethod"]"absolute""fixed" escapes sticky headers and other stacking contexts.
containerBaseTooltip.Portal.Props["container"]document.bodyThe element the popup renders into (a portal), for example a shadow root.
content *ReactNode–Text in the tooltip.
side"top" | "bottom" | "left" | "right""top"
align"start" | "center" | "end""center"
sideOffsetnumber8Pixels between the trigger and the tooltip, as on Popover and the menus.
delaynumber600Milliseconds before it opens on hover.
closeDelaynumber0Milliseconds before it closes.
renderComponentPropsWithoutRef<typeof BaseTooltip.Trigger>["render"]–The trigger element, for example render={<Button shape="square" aria-label="Delete" icon={…} />}. Without it, children go in a plain button.
childrenReactNode–
classNamestring–Class on the trigger, for layout.