Tooltip

A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.

Basic
Sides
With Icon
Long Content
Disabled
With Keyboard Shortcut
On Link
Formatted Content

Installation

Install the dependencies

pnpm add cn github:sveltery/base

Copy the component

Copy the following files into src/lib/components/ui/tooltip.

src/lib/components/ui/tooltip/TooltipContent.svelte
<script lang="ts">
  import type { ComponentProps } from 'svelte';
  import { Tooltip as Primitive } from '@sveltery/base/tooltip';
  import { cn } from 'cn';
  let {
    children,
    class: classProp,
    align = 'center',
    alignOffset = 0,
    side = 'top',
    sideOffset = 4,
    ...props
  }: ComponentProps<typeof Primitive.Popup> & Pick<ComponentProps<typeof Primitive.Positioner>, 'align' | 'alignOffset' | 'side' | 'sideOffset'> = $props();
</script>
<Primitive.Portal>
  <Primitive.Positioner {align} {alignOffset} {side} {sideOffset} class="isolate z-50">
    <Primitive.Popup data-slot="tooltip-content" {...props} class={cn('data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-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 inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs has-data-[slot=kbd]:pr-1.5 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 w-fit max-w-xs origin-(--transform-origin) bg-foreground text-background', classProp)}>
      {@render children?.()}
      <Primitive.Arrow class="size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] 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 z-50 bg-foreground fill-foreground data-[side=bottom]:top-1 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" />
    </Primitive.Popup>
  </Primitive.Positioner>
</Primitive.Portal>
src/lib/components/ui/tooltip/TooltipProvider.svelte
<script lang="ts">
  import type { ComponentProps } from 'svelte';
  import { Tooltip as Primitive } from '@sveltery/base/tooltip';
  let { delay = 0, ...props }: ComponentProps<typeof Primitive.Provider> = $props();
</script>
<Primitive.Provider {delay} {...props} />
src/lib/components/ui/tooltip/TooltipTrigger.svelte
<script lang="ts">
  import type { ComponentProps } from 'svelte';
  import { Tooltip as Primitive } from '@sveltery/base/tooltip';
  let { children, ...props }: ComponentProps<typeof Primitive.Trigger> = $props();
</script>
<Primitive.Trigger data-slot="tooltip-trigger" {children} {...props} />
src/lib/components/ui/tooltip/index.ts
import { Tooltip as Primitive } from '@sveltery/base/tooltip';

export const Tooltip = Primitive.Root;
export { default as TooltipProvider } from './TooltipProvider.svelte';
export { default as TooltipTrigger } from './TooltipTrigger.svelte';
export { default as TooltipContent } from './TooltipContent.svelte';

Update the import paths

Change the imports to match your project setup.

Usage

<script lang="ts">
  import { Tooltip, TooltipContent, TooltipTrigger } from '$lib/components/ui/tooltip';
</script>
<Tooltip>
  <TooltipTrigger>Hover</TooltipTrigger>
  <TooltipContent>
    <p>Add to library</p>
  </TooltipContent>
</Tooltip>

The tooltip opens when the pointer rests on the trigger or the trigger gets keyboard focus. Escape closes it. A tooltip only describes its trigger. Put anything a user must read or press in a Popover instead.

Provider

TooltipProvider shares delays across the tooltips inside it. Once one tooltip opens, the next opens at once while the pointer moves between triggers. It sets delay to 0 by default. Without a provider, a tooltip waits 600 ms, which you can change with delay on TooltipTrigger.

<TooltipProvider>
  <Tooltip>…</Tooltip>
  <Tooltip>…</Tooltip>
</TooltipProvider>

Trigger

TooltipTrigger renders a <button>. Pass class to style it, for example with buttonVariants. To use another element, pass a render snippet and spread its props onto that element. To show a tooltip on a disabled button, render the trigger as a <span> around the button, because a disabled button gets no pointer events.

API reference

Tooltip is styled on top of the Sveltery Base Tooltip primitive, which owns its behavior, positioning and accessibility. See the Base UI Tooltip documentation for the behavior it ports.