nori-ui

Tooltip

Small floating label triggered by hover (web) or long-press (native). For short contextual hints — most often on icon-only buttons.

4.1 kBgzipped

At a glance

  • Compose: Tooltip, TooltipTrigger, TooltipContent. Trigger uses asChild by default — wrap any element (Button, Link, custom Pressable) and it becomes the activator.
  • Cross-platform: web opens on hover or focus; native opens on long-press (500ms hold).
  • Configurable open / close delays via delayMs (default 500ms) and closeDelayMs (default 0ms).
  • Accessibility: trigger gets aria-describedby pointing at the content's id (the tooltip augments the trigger's accessible name — it does not replace it). Content gets role="tooltip" with a unique id.

Tooltip vs Popover

  • Tooltip — small hover/long-press hint with non-interactive text. Use for short labels like "Click to view details" on icon-only buttons. Augments the trigger's accessible name; not a focus stop.
  • Popover — anchored, non-modal, can hold interactive content (forms, buttons, color pickers). Dismisses on outside click + Escape. Use Popover when the surface needs to be clicked into.

Preview

Sides

TooltipContent accepts a side of 'top', 'right', 'bottom', or 'left' (default 'top'). Pair it with align ('start' | 'center' | 'end', default 'center') to fine-tune placement along the chosen edge.

Anatomy

SubcomponentRole
TooltipRoot — owns open state and the open / close timers.
TooltipTriggerElement that reveals the tooltip. asChild by default.
TooltipContentThe floating label. Renders only while open.

Delays

// Default: 500ms before opening, 0ms before closing.
<Tooltip>...</Tooltip>
 
// Snappy hint — open after 200ms.
<Tooltip delayMs={200}>...</Tooltip>
 
// Forgiving close — give the cursor 150ms to come back.
<Tooltip delayMs={300} closeDelayMs={150}>...</Tooltip>

Open state — open, defaultOpen, onOpenChange

Pass open for controlled mode and pair with onOpenChange. Pass defaultOpen for uncontrolled mode when you want the tooltip pre-opened on first render (rare, useful during onboarding tours). Mixing the two prefers controlled and ignores defaultOpen.

import { useState } from 'react';
import { Button, Tooltip } from '@nori-ui/core';
 
// Uncontrolled — Tooltip owns its open state.
export function Uncontrolled() {
    return (
        <Tooltip>
            <Tooltip.Trigger>
                <Button>Hover me</Button>
            </Tooltip.Trigger>
            <Tooltip.Content>Hint</Tooltip.Content>
        </Tooltip>
    );
}
 
// Controlled — you own the state, useful for forcing the tooltip open
// during onboarding tours or syncing with a "show all hints" setting.
export function Controlled() {
    const [open, setOpen] = useState(false);
    return (
        <Tooltip open={open} onOpenChange={setOpen}>
            <Tooltip.Trigger>
                <Button>Hover me</Button>
            </Tooltip.Trigger>
            <Tooltip.Content>Hint</Tooltip.Content>
        </Tooltip>
    );
}

Accessibility

The trigger receives aria-describedby referencing the content's id only while the tooltip is open. This means the tooltip text is announced in addition to the trigger's existing accessible name — so an icon-only button still needs its own aria-label:

<Tooltip>
    <Tooltip.Trigger>
        <Button leadingIcon={InfoIcon} aria-label="More info" />
    </Tooltip.Trigger>
    <Tooltip.Content>Click to view details</Tooltip.Content>
</Tooltip>

The content has role="tooltip" and is non-interactive (pointerEvents: 'none') — if you need a panel users can click into, reach for Popover instead. Pressing Escape while the tooltip is open dismisses it.

Native behavior

On iOS and Android there is no hover, so the tooltip opens on a 500ms long-press of the trigger and dismisses on the next tap anywhere. The chip is rendered with absolute positioning relative to where it sits in the tree — the parent needs to allow overflow for the chip to peek out. For richer native overlays, prefer Popover.

Props

Tooltip

PropTypeDefaultDescription
closeDelayMsnumber0Delay before the tooltip closes after hover-out / blur. Useful for giving users time to move into the tooltip if it ever becomes interactive (it shouldn't — use Popover for that — but the knob is here so the API matches Radix). @defaultValue 0
defaultOpenbooleanfalseUncontrolled initial open state. @defaultValue false
delayMsnumber500Delay before the tooltip opens after hover/focus. @defaultValue 500
onOpenChange(open: boolean) => voidFires with the new open state.
openbooleanControlled open state.

TooltipTrigger

PropTypeDefaultDescription
asChildbooleantrueRender the child as the trigger (Slot pattern). Default true — pass `false` for an inline pressable.
classNamestring
testIDstring

TooltipContent

PropTypeDefaultDescription
alignenumcenterAlignment along the trigger edge. @defaultValue 'center'
classNamestring
sideenumtopSide of the trigger to anchor on. @defaultValue 'top'
testIDstring

On this page

Preview theme