Svelte 5 component · fractalsvelte-ui · Overlays
Tooltip
A transient informational tooltip triggered on hover or focus with configurable delay thresholds. Automatically positions around the target element with directional arrow pointers.
Install
Tooltip is a Svelte 5 tooltip component in @fractaldesign/fractalsvelte-ui. It works in SvelteKit and in any Svelte 5 app.
pnpm add @fractaldesign/fractalsvelte-uiImport
import { Tooltip } from '@fractaldesign/fractalsvelte-ui';Usage
<Tooltip placement={'top'} delay={200}>Click me</Tooltip>Props
Also accepts every attribute of HTMLAttributes, spread onto
the root element.
| Name | Type | Default |
|---|---|---|
content | string Plain text content to display | — |
placement | TooltipPlacement 'top' | 'bottom' | 'left' | 'right' Position placement relative to trigger | 'top' |
delay | number Milliseconds to wait before displaying on hover | 200 |
arrow | boolean Render pointer arrow pointing toward trigger | true |
disabled | boolean Disable tooltip appearance | false |
class | string Additional CSS classes for tooltip panel | '' |
Snippets
| Name | Type | Default |
|---|---|---|
tooltipSnippet | Snippet Custom rich HTML tooltip content | — |
children | Snippet Trigger element wrapped by tooltip | — |
Source
Show the Tooltip.svelte source
<script lang="ts">
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
interface Props extends HTMLAttributes<HTMLDivElement> {
/** Plain text content to display */
content?: string;
/** Position placement relative to trigger */
placement?: TooltipPlacement;
/** Milliseconds to wait before displaying on hover */
delay?: number;
/** Render pointer arrow pointing toward trigger */
arrow?: boolean;
/** Disable tooltip appearance */
disabled?: boolean;
/** Additional CSS classes for tooltip panel */
class?: string;
/** Custom rich HTML tooltip content */
tooltipSnippet?: Snippet;
/** Trigger element wrapped by tooltip */
children?: Snippet;
}
let {
content,
placement = 'top',
delay = 200,
arrow = true,
disabled = false,
class: className = '',
tooltipSnippet,
children,
...restProps
}: Props = $props();
let visible = $state(false);
let timeoutId: ReturnType<typeof setTimeout> | null = null;
const tooltipId = `f-tooltip-${Math.random().toString(36).slice(2, 9)}`;
function show() {
if (disabled || (!content && !tooltipSnippet)) return;
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
visible = true;
}, delay);
}
function hide() {
if (timeoutId) clearTimeout(timeoutId);
visible = false;
}
</script>
<div
class="f-tooltip-wrapper"
role="presentation"
onmouseenter={show}
onmouseleave={hide}
onfocusin={show}
onfocusout={hide}
aria-describedby={visible ? tooltipId : undefined}
>
{#if children}
{@render children()}
{/if}
{#if visible}
<div
id={tooltipId}
role="tooltip"
class="f-tooltip-panel {className}"
data-placement={placement}
data-arrow={arrow ? 'true' : 'false'}
{...restProps}
>
{#if arrow}
<span class="f-tooltip-arrow" aria-hidden="true"></span>
{/if}
{#if tooltipSnippet}
{@render tooltipSnippet()}
{:else}
{content}
{/if}
</div>
{/if}
</div>
<style>
@layer fractalsvelte-ui.components {
.f-tooltip-wrapper {
position: relative;
display: inline-flex;
}
.f-tooltip-panel {
position: absolute;
z-index: 1000;
padding: var(--f-space-3xs) var(--f-space-sm);
font-size: var(--f-text-xs);
font-weight: 500;
line-height: 1.4;
white-space: nowrap;
background: var(--f-text);
color: var(--f-text-inverse);
border-radius: var(--f-radius-sm);
box-shadow: var(--f-shadow-md);
pointer-events: none;
animation: f-tooltip-fade var(--f-duration-fast) var(--f-ease);
}
/* Placement offsets */
.f-tooltip-panel[data-placement='top'] {
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
}
.f-tooltip-panel[data-placement='bottom'] {
top: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
}
.f-tooltip-panel[data-placement='left'] {
right: calc(100% + 6px);
top: 50%;
transform: translateY(-50%);
}
.f-tooltip-panel[data-placement='right'] {
left: calc(100% + 6px);
top: 50%;
transform: translateY(-50%);
}
/* Arrow */
.f-tooltip-arrow {
position: absolute;
width: 6px;
height: 6px;
background: var(--f-text);
transform: rotate(45deg);
}
.f-tooltip-panel[data-placement='top'] .f-tooltip-arrow {
bottom: -3px;
left: 50%;
margin-left: -3px;
}
.f-tooltip-panel[data-placement='bottom'] .f-tooltip-arrow {
top: -3px;
left: 50%;
margin-left: -3px;
}
.f-tooltip-panel[data-placement='left'] .f-tooltip-arrow {
right: -3px;
top: 50%;
margin-top: -3px;
}
.f-tooltip-panel[data-placement='right'] .f-tooltip-arrow {
left: -3px;
top: 50%;
margin-top: -3px;
}
@keyframes f-tooltip-fade {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
}
</style>Related components
AlertDialog A high-priority confirmation dialog designed for destructive or critical actions.CenterMorphModal A smooth expanding modal dialog that morphs directly from its originating trigger button with backdrop blur,…CommandPalette A searchable command palette modal opened via a global keyboard shortcut.ContextMenu A floating context menu triggered on mouse right-click, anchored to pointer coordinates with support for…Dialog An accessible modal dialog featuring backdrop blur, focus trapping, Escape key dismissal, and scale entrance…Drawer A slide-out side panel that docks to any screen edge with backdrop blur and touch or click dismissal.