rapidUI

Tooltip

Hover/focus hint built on Base UI Tooltip.

Installation

pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/tooltip.json

Installs to @/components/ui/tooltip.

Manual installation

Install the dependencies:

pnpm add @base-ui/react@^1.8.0

Copy the source into your project:

components/ui/tooltip.tsx
'use client'

import { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip'
import { cn } from '@/lib/utils'

function TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) {
	return <TooltipPrimitive.Provider data-slot="tooltip-provider" delay={delay} {...props} />
}

function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
	return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
}

function TooltipTrigger({ className, ...props }: TooltipPrimitive.Trigger.Props) {
	return (
		<TooltipPrimitive.Trigger
			data-slot="tooltip-trigger"
			className={cn('focus-visible:ring-3 focus-visible:ring-ring focus-visible:outline-hidden', className)}
			{...props}
		/>
	)
}

function TooltipContent({
	className,
	side = 'top',
	sideOffset = 4,
	align = 'center',
	alignOffset = 0,
	children,
	...props
}: TooltipPrimitive.Popup.Props &
	Pick<TooltipPrimitive.Positioner.Props, 'align' | 'alignOffset' | 'side' | 'sideOffset'>) {
	return (
		<TooltipPrimitive.Portal>
			<TooltipPrimitive.Positioner
				align={align}
				alignOffset={alignOffset}
				side={side}
				sideOffset={sideOffset}
				className="isolate z-50"
			>
				<TooltipPrimitive.Popup
					data-slot="tooltip-content"
					className={cn(
						'z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-popover px-3 py-1.5 text-xs text-popover-foreground ring-1 ring-border has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
						className,
					)}
					{...props}
				>
					{children}
					<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-popover fill-popover data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
				</TooltipPrimitive.Popup>
			</TooltipPrimitive.Positioner>
		</TooltipPrimitive.Portal>
	)
}

export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }

A tooltip component that displays additional information when hovering over or focusing on an element. Built on top of Base UI Tooltip, it follows WAI-ARIA design patterns and provides a consistent way to show supplementary information.

Features

  • Accessible tooltip implementation
  • Customizable open and close delay
  • Multiple positioning options
  • Keyboard navigation support
  • Screen reader announcements
  • Custom content support
  • Controlled and uncontrolled modes
  • Focus management
  • Hover and focus interactions
  • Popover-coloured surface with a subtle border and an arrow
  • Inline keyboard shortcuts with Kbd
  • Requires a TooltipProvider ancestor (typically once at the app root)

Usage

Tooltip no longer renders its own TooltipProvider. Wrap your application (or the relevant subtree) in a single TooltipProvider; it defaults to delay={0}.

Pass the trigger element with the render prop (Base UI has no asChild): <TooltipTrigger render={<Button variant="outline" />}>Save draft</TooltipTrigger>.

import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from '@/components/ui/tooltip'
import { Button } from '@/components/ui/button'
import { Kbd } from '@/components/ui/kbd'
import { SaveIcon } from 'lucide-react'

// Basic usage
export function BasicTooltip() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger render={<Button variant="outline" />}>Save draft</TooltipTrigger>
        <TooltipContent>
          <p>Drafts are kept for 30 days</p>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}

// With custom delay (per trigger, or for all tooltips via TooltipProvider)
export function DelayedTooltip() {
  return (
    <Tooltip>
      <TooltipTrigger delay={1000} render={<Button variant="outline" />}>Print</TooltipTrigger>
      <TooltipContent>
        <p>Prints the case summary</p>
      </TooltipContent>
    </Tooltip>
  )
}

// With keyboard shortcut
export function ShortcutTooltip() {
  return (
    <Tooltip>
      <TooltipTrigger render={<Button variant="outline" size="icon-sm" aria-label="Save" />}>
          <SaveIcon />
        </TooltipTrigger>
      <TooltipContent side="bottom">
        Save changes <Kbd>⌘S</Kbd>
      </TooltipContent>
    </Tooltip>
  )
}

// With rich content
export function RichTooltip() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger render={<Button variant="outline" />}>Sofia Berg</TooltipTrigger>
        <TooltipContent>
          <div className="space-y-2">
            <p className="font-medium">Sofia Berg</p>
            <p className="text-muted-foreground">Case handler, building permits</p>
          </div>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}

Examples

Default Tooltip

A short hint on an outline button.

With Icon

A case toolbar of icon-only buttons. Each has an aria-label, and the tooltip shows the same text to sighted users.

Custom Delay

delay per trigger (or for all tooltips on TooltipProvider). The provider defaults to 0.

Rich Content

Several lines of content – the name, role and availability of each handler on a case.

Plain Tooltip

A plain-text trigger inside running text that explains an abbreviation. Long content wraps at max-w-xs.

Sides

Place the tooltip with side: top, right, bottom or left.

With Keyboard Shortcut

Kbd and KbdGroup inside the content get tooltip-specific styling. Mirror the shortcut in aria-keyshortcuts.

Disabled Button

A disabled button can't be focused or hovered, so its tooltip never shows. Use aria-disabled="true" instead: the button stays focusable, the tooltip explains why the action is unavailable, and the click handler does nothing.

Field Hint

A small info button next to a field label, with guidance on where to find the value.

Props

TooltipProvider

  • delay - Delay in ms before tooltips open (default 0)
  • closeDelay - Delay in ms before tooltips close
  • timeout - Time in ms during which moving to another trigger opens its tooltip instantly

Tooltip

  • open / defaultOpen / onOpenChange - Open state
  • disabled - Stops the tooltip from opening

TooltipTrigger

  • render - The element to render as the trigger, e.g. render={<Button variant="outline" />} (replaces asChild)
  • delay / closeDelay - Override the provider delays for this trigger
  • closeOnClick - Whether clicking the trigger closes the tooltip (default true)

TooltipContent

  • side - 'top' | 'right' | 'bottom' | 'left' | 'inline-start' | 'inline-end' (default 'top')
  • sideOffset - Distance from the trigger (default 4)
  • align / alignOffset - Alignment relative to the trigger
  • className - Additional CSS classes

Accessibility

The Tooltip component follows WAI-ARIA guidelines for tooltips:

  • Uses role="tooltip" for proper semantic meaning
  • Manages focus appropriately
  • Supports keyboard navigation
  • Announces content to screen readers
  • Provides sufficient contrast for visibility
  • Maintains proper timing for display

Best Practices

  1. Keep tooltip content concise and helpful
  2. Use tooltips sparingly to avoid overwhelming users
  3. Ensure tooltip content adds value beyond what's visible
  4. Consider mobile users and touch interactions
  5. Test with keyboard navigation and screen readers
  6. Maintain appropriate delay duration for user experience
  7. Use consistent styling across your application