Tooltip
A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.
<script lang="ts">
import { buttonVariants } from "$lib/components/ui/button/index.js";
import * as Tooltip from "$lib/components/ui/tooltip/index.js";
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger class={buttonVariants({ variant: "outline" })}
>Hover</Tooltip.Trigger
>
<Tooltip.Content>
<p>Add to library</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider> Installation
pnpm dlx shadcn-svelte@latest add tooltip Install bits-ui:
pnpm add bits-ui -D Copy and paste the following code into your project.
import Root from './tooltip.svelte';
import Trigger from './tooltip-trigger.svelte';
import Content from './tooltip-content.svelte';
import Provider from './tooltip-provider.svelte';
import Portal from './tooltip-portal.svelte';
export {
Root,
Trigger,
Content,
Provider,
Portal,
//
Root as Tooltip,
Content as TooltipContent,
Trigger as TooltipTrigger,
Provider as TooltipProvider,
Portal as TooltipPortal
};
<script lang="ts">
import { Tooltip as TooltipPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
import TooltipPortal from './tooltip-portal.svelte';
import type { ComponentProps } from 'svelte';
import type { WithoutChildrenOrChild } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
sideOffset = 0,
side = 'top',
children,
arrowClasses,
portalProps,
...restProps
}: TooltipPrimitive.ContentProps & {
arrowClasses?: string;
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof TooltipPortal>>;
} = $props();
</script>
<TooltipPortal {...portalProps}>
<TooltipPrimitive.Content
bind:ref
data-slot="tooltip-content"
{sideOffset}
{side}
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 z-50 inline-flex w-fit max-w-xs origin-(--bits-tooltip-content-transform-origin) items-center gap-1.5 border border-[#222225] bg-[#09090b] px-3 py-1.5 font-mono text-xs uppercase tracking-[0.04em] text-zinc-50 shadow-none has-data-[slot=kbd]:pr-1.5 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-none',
className
)}
{...restProps}
>
{@render children?.()}
<TooltipPrimitive.Arrow>
{#snippet child({ props })}
<div
class={cn(
'z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 border-r border-b border-[#222225] bg-[#09090b] fill-[#09090b]',
'data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%+2px)]',
'data-[side=bottom]:-translate-x-1/2 data-[side=bottom]:-translate-y-[calc(-50%+1px)]',
'data-[side=right]:translate-x-[calc(50%+2px)] data-[side=right]:translate-y-1/2',
'data-[side=left]:-translate-y-[calc(50%-3px)]',
arrowClasses
)}
{...props}
></div>
{/snippet}
</TooltipPrimitive.Arrow>
</TooltipPrimitive.Content>
</TooltipPortal>
<script lang="ts">
import { Tooltip as TooltipPrimitive } from 'bits-ui';
let { ...restProps }: TooltipPrimitive.PortalProps = $props();
</script>
<TooltipPrimitive.Portal {...restProps} />
<script lang="ts">
import { Tooltip as TooltipPrimitive } from 'bits-ui';
let { delayDuration = 0, ...restProps }: TooltipPrimitive.ProviderProps = $props();
</script>
<TooltipPrimitive.Provider {delayDuration} {...restProps} />
<script lang="ts">
import { Tooltip as TooltipPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: TooltipPrimitive.TriggerProps = $props();
</script>
<TooltipPrimitive.Trigger
bind:ref
data-slot="tooltip-trigger"
class={cn(
'outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring/30',
className
)}
{...restProps}
/>
<script lang="ts">
import { Tooltip as TooltipPrimitive } from 'bits-ui';
let { open = $bindable(false), ...restProps }: TooltipPrimitive.RootProps = $props();
</script>
<TooltipPrimitive.Root bind:open {...restProps} />
Usage
The Tooltip.Provider component should be placed once in your root layout, wrapping all content that will contain tooltips. This ensures that only one tooltip within the provider can be open at a time.
<script lang="ts">
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
let { children } = $props();
</script> <Tooltip.Provider>
{@render children()}
</Tooltip.Provider> Then use tooltips anywhere in your app:
<script lang="ts">
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
</script>
<Tooltip.Root>
<Tooltip.Trigger>Hover</Tooltip.Trigger>
<Tooltip.Content>
<p>Add to library</p>
</Tooltip.Content>
</Tooltip.Root> Nested Providers
You can nest providers to create groups with different settings. Tooltips use the closest ancestor provider. This is useful when you want instant tooltips in specific areas:
<Tooltip.Provider delayDuration={0}>
<!-- Tooltips here will open instantly -->
</Tooltip.Provider> Changelog
2025-12 Update tooltip colors
We've updated the tooltip colors to use the foreground color for the background and the background color for the foreground.
Replace bg-primary text-primary-foreground with bg-foreground text-background for <Tooltip.Content />.