Tooltip
Tooltip is a thin wrapper over Ark UI tooltip that shows supplementary text on hover or focus. It is styled with the helical-ui theme (bg-popover/text-popover-foreground content, a neutral surface) and accessibility comes entirely from Ark’s tooltip state machine — the content carries role="tooltip" and is associated with its trigger. The portal and positioner are internal details; the arrow is toggled with the arrow prop on TooltipContent and only rendered while the tooltip is open.
// apps/docs/src/components/examples/tooltip/default/react.tsx
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-react';
export default function ReactTooltipDefault() {
return (
<Tooltip>
<TooltipTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>I am a tooltip</TooltipContent>
</Tooltip>
);
}<!-- apps/docs/src/components/examples/tooltip/default/svelte.svelte -->
<script lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-svelte';
</script>
<Tooltip>
<TooltipTrigger class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>I am a tooltip</TooltipContent>
</Tooltip>Examples
Arrow
// apps/docs/src/components/examples/tooltip/arrow/react.tsx
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-react';
export default function ReactTooltipArrow() {
return (
<div className="flex items-center gap-6">
<Tooltip>
<TooltipTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
With arrow
</TooltipTrigger>
<TooltipContent>Arrow on by default</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger className="rounded-md bg-secondary px-3 py-1.5 text-sm font-medium text-secondary-foreground">
Without arrow
</TooltipTrigger>
<TooltipContent arrow={false}>No arrow on this tooltip</TooltipContent>
</Tooltip>
</div>
);
}<!-- apps/docs/src/components/examples/tooltip/arrow/svelte.svelte -->
<script lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-svelte';
</script>
<div class="flex items-center gap-6">
<Tooltip>
<TooltipTrigger class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
With arrow
</TooltipTrigger>
<TooltipContent>Arrow on by default</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger class="rounded-md bg-secondary px-3 py-1.5 text-sm font-medium text-secondary-foreground">
Without arrow
</TooltipTrigger>
<TooltipContent arrow={false}>No arrow on this tooltip</TooltipContent>
</Tooltip>
</div>Context
Tooltip is closed
Tooltip is closed
// apps/docs/src/components/examples/tooltip/context/react.tsx
import { Tooltip, TooltipTrigger, TooltipContent, useTooltipContext } from '@cloudvoyant/helical-react';
function TooltipStatus() {
const tooltip = useTooltipContext();
return <p className="text-sm text-muted-foreground">Tooltip is {tooltip.open ? 'open' : 'closed'}</p>;
}
export default function ReactTooltipContext() {
return (
<Tooltip>
<div className="flex items-center gap-4">
<TooltipTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipStatus />
</div>
<TooltipContent>Context tooltip</TooltipContent>
</Tooltip>
);
}<!-- apps/docs/src/components/examples/tooltip/context/svelte.svelte -->
<script lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent, TooltipContext } from '@cloudvoyant/helical-svelte';
</script>
<Tooltip>
<div class="flex items-center gap-4">
<TooltipTrigger class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContext>
{#snippet render(tooltip)}
<p class="text-sm text-muted-foreground">Tooltip is {tooltip().open ? 'open' : 'closed'}</p>
{/snippet}
</TooltipContext>
</div>
<TooltipContent>Context tooltip</TooltipContent>
</Tooltip>Controlled
// apps/docs/src/components/examples/tooltip/controlled/react.tsx
import { useState } from 'react';
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-react';
export default function ReactTooltipControlled() {
const [open, setOpen] = useState(false);
return (
<div className="flex items-center gap-4">
<Tooltip open={open} onOpenChange={(e) => setOpen(e.open)}>
<TooltipTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>Controlled tooltip</TooltipContent>
</Tooltip>
<label className="flex items-center gap-2 text-sm text-muted-foreground">
<input type="checkbox" checked={open} onChange={(e) => setOpen(e.target.checked)} />
Open
</label>
</div>
);
}<!-- apps/docs/src/components/examples/tooltip/controlled/svelte.svelte -->
<script lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-svelte';
let open = $state(false);
</script>
<div class="flex items-center gap-4">
<Tooltip bind:open>
<TooltipTrigger class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>Controlled tooltip</TooltipContent>
</Tooltip>
<label class="flex items-center gap-2 text-sm text-muted-foreground">
<input type="checkbox" bind:checked={open} />
Open
</label>
</div>Delay
// apps/docs/src/components/examples/tooltip/delay/react.tsx
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-react';
export default function ReactTooltipDelay() {
return (
<Tooltip openDelay={1000} closeDelay={200}>
<TooltipTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me (1s open delay)
</TooltipTrigger>
<TooltipContent>Delayed tooltip</TooltipContent>
</Tooltip>
);
}<!-- apps/docs/src/components/examples/tooltip/delay/svelte.svelte -->
<script lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-svelte';
</script>
<Tooltip openDelay={1000} closeDelay={200}>
<TooltipTrigger class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me (1s open delay)
</TooltipTrigger>
<TooltipContent>Delayed tooltip</TooltipContent>
</Tooltip>Following Cursor
// apps/docs/src/components/examples/tooltip/following-cursor/react.tsx
import { Tooltip, TooltipTrigger, TooltipContent, useTooltipContext } from '@cloudvoyant/helical-react';
const cursor = { x: 0, y: 0 };
let areaEl: HTMLDivElement | null = null;
function FollowArea() {
const tooltip = useTooltipContext();
return (
<div
ref={(el) => {
areaEl = el;
}}
className="rounded-md border p-10 text-center"
onPointerMove={(e) => {
const rect = areaEl?.getBoundingClientRect();
if (!rect) return;
cursor.x = e.clientX - rect.left;
cursor.y = e.clientY - rect.top + 12;
tooltip.reposition();
}}
>
<TooltipTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Move your cursor here
</TooltipTrigger>
</div>
);
}
export default function ReactTooltipFollowingCursor() {
return (
<Tooltip
positioning={{
strategy: 'fixed',
getAnchorRect: () => {
const rect = areaEl?.getBoundingClientRect();
return new DOMRect((rect?.left ?? 0) + cursor.x, (rect?.top ?? 0) + cursor.y, 1, 1);
},
}}
>
<FollowArea />
<TooltipContent>Follows your cursor</TooltipContent>
</Tooltip>
);
}<!-- apps/docs/src/components/examples/tooltip/following-cursor/svelte.svelte -->
<script lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent, TooltipContext } from '@cloudvoyant/helical-svelte';
let areaEl: HTMLDivElement | undefined = $state();
const cursor = { x: 0, y: 0 };
</script>
<Tooltip
positioning={{
strategy: 'fixed',
getAnchorRect: () => {
const rect = areaEl?.getBoundingClientRect();
return new DOMRect((rect?.left ?? 0) + cursor.x, (rect?.top ?? 0) + cursor.y, 1, 1);
},
}}
>
<TooltipContext>
{#snippet render(tooltip)}
<div
bind:this={areaEl}
class="rounded-md border p-10 text-center"
onpointermove={(e) => {
const rect = areaEl?.getBoundingClientRect();
if (!rect) return;
cursor.x = e.clientX - rect.left;
cursor.y = e.clientY - rect.top + 12;
tooltip().reposition();
}}
>
<TooltipTrigger class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Move your cursor here
</TooltipTrigger>
</div>
{/snippet}
</TooltipContext>
<TooltipContent>Follows your cursor</TooltipContent>
</Tooltip>Multiple Triggers
// apps/docs/src/components/examples/tooltip/multiple-triggers/react.tsx
import { useState } from 'react';
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-react';
const items = ['First', 'Second', 'Third'];
export default function ReactTooltipMultipleTriggers() {
const [active, setActive] = useState('First');
return (
<Tooltip onTriggerValueChange={(e) => setActive(e.value ?? 'First')}>
<div className="flex items-center gap-2">
{items.map((item) => (
<TooltipTrigger
key={item}
value={item}
className="rounded-md bg-secondary px-3 py-1.5 text-sm font-medium text-secondary-foreground"
>
{item}
</TooltipTrigger>
))}
</div>
<TooltipContent>This tooltip follows the active trigger ({active})</TooltipContent>
</Tooltip>
);
}<!-- apps/docs/src/components/examples/tooltip/multiple-triggers/svelte.svelte -->
<script lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-svelte';
const items = ['First', 'Second', 'Third'];
let active = $state('First');
</script>
<Tooltip onTriggerValueChange={(e) => (active = e.value ?? 'First')}>
<div class="flex items-center gap-2">
{#each items as item (item)}
<TooltipTrigger
value={item}
class="rounded-md bg-secondary px-3 py-1.5 text-sm font-medium text-secondary-foreground"
>
{item}
</TooltipTrigger>
{/each}
</div>
<TooltipContent>This tooltip follows the active trigger ({active})</TooltipContent>
</Tooltip>Positioning
// apps/docs/src/components/examples/tooltip/positioning/react.tsx
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-react';
export default function ReactTooltipPositioning() {
return (
<Tooltip positioning={{ placement: 'right', offset: { mainAxis: 12 } }}>
<TooltipTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>Positioned on the right</TooltipContent>
</Tooltip>
);
}<!-- apps/docs/src/components/examples/tooltip/positioning/svelte.svelte -->
<script lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-svelte';
</script>
<Tooltip positioning={{ placement: 'right', offset: { mainAxis: 12 } }}>
<TooltipTrigger class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>Positioned on the right</TooltipContent>
</Tooltip>Root Provider
Tooltip is closed
Tooltip is closed
// apps/docs/src/components/examples/tooltip/root-provider/react.tsx
import { useTooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-react';
export default function ReactTooltipRootProvider() {
const tooltip = useTooltip();
return (
<div className="flex flex-col items-center gap-4">
<p className="text-sm text-muted-foreground">Tooltip is {tooltip.open ? 'open' : 'closed'}</p>
<TooltipProvider value={tooltip}>
<TooltipTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>Controlled externally</TooltipContent>
</TooltipProvider>
</div>
);
}<!-- apps/docs/src/components/examples/tooltip/root-provider/svelte.svelte -->
<script lang="ts">
import { useTooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-svelte';
const tooltip = useTooltip({ id: 'tooltip-1' });
</script>
<div class="flex flex-col items-center gap-4">
<p class="text-sm text-muted-foreground">Tooltip is {tooltip().open ? 'open' : 'closed'}</p>
<TooltipProvider value={tooltip}>
<TooltipTrigger class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>Controlled externally</TooltipContent>
</TooltipProvider>
</div>Within Fixed Containers
Rendered inside a fixed container
Rendered inside a fixed container
// apps/docs/src/components/examples/tooltip/within-fixed-containers/react.tsx
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-react';
export default function ReactTooltipWithinFixedContainers() {
return (
<div className="flex flex-col gap-2 rounded-md border p-4">
<p className="text-sm text-muted-foreground">Rendered inside a fixed container</p>
<Tooltip positioning={{ strategy: 'fixed' }}>
<TooltipTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>Fixed strategy tooltip</TooltipContent>
</Tooltip>
</div>
);
}<!-- apps/docs/src/components/examples/tooltip/within-fixed-containers/svelte.svelte -->
<script lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent } from '@cloudvoyant/helical-svelte';
</script>
<div class="flex flex-col gap-2 rounded-md border p-4">
<p class="text-sm text-muted-foreground">Rendered inside a fixed container</p>
<Tooltip positioning={{ strategy: 'fixed' }}>
<TooltipTrigger class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground">
Hover me
</TooltipTrigger>
<TooltipContent>Fixed strategy tooltip</TooltipContent>
</Tooltip>
</div>API Reference
Tooltip
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open / onOpenChange |
boolean / (details) => void |
— | Controlled open state. |
defaultOpen |
boolean |
false |
Initial open state when uncontrolled. |
openDelay / closeDelay |
number |
400 / 150 |
Open/close delay in ms. |
positioning |
PositioningOptions |
— | Placement/offset (e.g. { placement: 'right' }, { strategy: 'fixed' }). |
disabled |
boolean |
false |
Disables the tooltip. |
interactive |
boolean |
false |
Keep open while hovering the content. |
closeOnEscape |
boolean |
true |
Close on Escape. |
Accepts all Ark TooltipRootProps.
TooltipProvider
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value |
UseTooltipReturn |
— | The API object from useTooltip(). |
TooltipTrigger
Accepts all Ark TooltipTriggerProps plus className/class.
TooltipContent
Props
| Prop | Type | Default | Description |
|---|---|---|---|
arrow |
boolean |
true |
Render the arrow pointing at the trigger. |
Accepts all Ark TooltipContentProps plus className/class.
Hooks
useTooltip— Ark machine factory; returnsUseTooltipReturnforTooltipProvider.useTooltipContext— reads the current tooltip state from context.
Accessibility
Follows the Tooltip WAI-ARIA pattern via Ark. Keyboard support: Tab opens/closes the tooltip without delay; Escape closes it.
Theme extensions
None. The tooltip reuses existing tokens (--popover, --popover-foreground, --border) — no theme.css changes.