Tooltip

A short label naming the control under the finger — opened by a long press, gone on its own, and never in the way of the next tap.

Open Source

Installation

Tooltip ships with the library — install it once, then import:

import { Tooltip } from "@delacour/react-native-ui/tooltip";

Or copy the source into your project, to own and edit it:

bunx delacour@latest add tooltip

Tooltip draws through the overlay layer, so mount OverlayProvider once at the app root. The provider is also what notices a tap elsewhere and closes the tooltip.

Usage

app/index.tsx
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
import { IconShareOs } from "@/lib/icons/central";
import { Tooltip } from "@/components/ui/tooltip";
import { Share, View } from "react-native";

function share() {
  Share.share({ message: "https://ui.delacour.co.nz" });
}

export default function Example() {
  return (
    <View className="flex-1 items-center justify-center p-4">
      <Tooltip label="Share">
        <Tooltip.Trigger asChild>
          <Button onPress={share} size="icon-md" variant="ghost">
            <Icon icon={IconShareOs} />
          </Button>
        </Tooltip.Trigger>
        <Tooltip.Content>
          <Tooltip.Arrow />
          <Tooltip.Text>Share</Tooltip.Text>
        </Tooltip.Content>
      </Tooltip>
    </View>
  );
}

A long press shows the label with a light haptic. A plain tap still presses the button.

Icon buttons

Anatomy

<Tooltip>
  <Tooltip.Trigger />
  <Tooltip.Content>
    <Tooltip.Arrow />
    <Tooltip.Text />
    <Tooltip.Title />
    <Tooltip.Description />
  </Tooltip.Content>
</Tooltip>

Tooltip.Text is the one-line label. Tooltip.Title and Tooltip.Description are for the surface variant, which has room for both.

Opening

Phones have no hover, so a tooltip opens on a long press by default and leaves the trigger's tap alone — the trigger's own onLongPress still runs too. openOn="press" opens it on a tap instead, for a trigger with no tap of its own, like an info glyph:

Open on press
<Tooltip label="Includes photos, files and backups" openOn="press">
  <Tooltip.Trigger accessibilityLabel="About storage" hitSlop={12}>
    <Icon color="muted-foreground" icon={IconCircleInfo} size="sm" />
  </Tooltip.Trigger>
  <Tooltip.Content>
    <Tooltip.Arrow />
    <Tooltip.Text>Includes photos, files and backups</Tooltip.Text>
  </Tooltip.Content>
</Tooltip>

Opening one tooltip closes any other.

Hiding

A tooltip hides itself duration milliseconds after it appears — 1500 by default. duration={0} keeps it until the trigger is used again or something else is tapped.

Persistent

A tap anywhere else closes it and still lands: the button you tapped is pressed, the list you touched scrolls. A tooltip is never something you have to dismiss before you can carry on. The tooltip itself takes no touch either.

<Tooltip duration={0} openOn="press">…</Tooltip>

isOpen and onOpenChange control it from outside; it still hides itself after duration.

Controlled

Placement

placement is the side the label prefers — top by default — and align lines it up along that side. It is placed exactly as a Popover is: it flips when that side lacks room, slides along the trigger to stay inside the safe area, and the arrow keeps pointing at the trigger's centre.

Placements
<Tooltip.Content align="start" offset={10} placement="bottom">…</Tooltip.Content>

Variants

inverted, the default, is a compact chip in the foreground colour that reads over anything in light and dark. surface is the popover card, with room for a title and a line.

Surface
<Tooltip.Content className="max-w-64" variant="surface">
  <Tooltip.Arrow />
  <Tooltip.Title>Sync</Tooltip.Title>
  <Tooltip.Description>Changes reach your other devices within a minute.</Tooltip.Description>
</Tooltip.Content>

The text parts take their colour from the variant, so neither needs a class.

Accessibility

Give the tooltip a label. It becomes the trigger's accessibility label when the trigger has none — an icon button — or its accessibility hint when it does, so VoiceOver and TalkBack read the tooltip's words without anything opening. The tooltip itself is hidden from assistive technology and never takes focus, so nothing is read twice.

With a screen reader running, a long press does not open the tooltip; a press tooltip still opens, and stays until it is dismissed. Under Reduce Motion it fades without sliding. Android's back button closes it.

API Reference

Tooltip

Prop

Type

Tooltip.Trigger

Takes Pressable's props. With asChild it hands its long press (or press) to its child instead of wrapping it, so the child must be built on Pressable — a Button, a Chip. The child's own handlers still run.

Tooltip.Content

Takes React Native's ViewProps.

Prop

Type

Tooltip.Arrow

Prop

Type

Tooltip.Text, Tooltip.Title, Tooltip.Description

Take Text's props. Tooltip.Text and Tooltip.Description are Text.Caption; Tooltip.Title is Text.Label.

useTooltip

Reads the enclosing tooltip. Throws outside a Tooltip.

Prop

Type

On this page