Tooltip

Provides additional information or context when users hover over or interact with an element.

llms.txt
	<script lang="ts">
  import { Tooltip } from "bits-ui";
  import MagicWand from "phosphor-svelte/lib/MagicWand";
</script>
 
<Tooltip.Provider>
  <Tooltip.Root delayDuration={200}>
    <Tooltip.Trigger
      class="border-border-input bg-background-alt shadow-btn ring-dark ring-offset-background
		hover:bg-muted focus-visible:ring-dark focus-visible:ring-offset-background focus-visible:outline-hidden inline-flex size-10 items-center justify-center rounded-full border focus-visible:ring-2 focus-visible:ring-offset-2"
    >
      <MagicWand class="size-5" />
    </Tooltip.Trigger>
    <Tooltip.Content sideOffset={8}>
      <div
        class="rounded-input border-dark-10 bg-background shadow-popover outline-hidden z-0 flex items-center justify-center border p-3 text-sm font-medium"
      >
        Make some magic!
      </div>
    </Tooltip.Content>
  </Tooltip.Root>
</Tooltip.Provider>

Structure

	<script lang="ts">
  import { Tooltip } from "bits-ui";
</script>
 
<Tooltip.Provider>
  <Tooltip.Root>
    <Tooltip.Trigger />
    <Tooltip.Portal>
      <Tooltip.Content>
        <Tooltip.Arrow />
      </Tooltip.Content>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>

Provider Component

The Tooltip.Provider component is required to be an ancestor of the Tooltip.Root component. It provides shared state for the tooltip components used within it. You can set a single delayDuration or disableHoverableContent prop on the provider component to apply to all the tooltip components within it.

	<script lang="ts">
  import { Tooltip } from "bits-ui";
</script>
 
<Tooltip.Provider delayDuration={0} disableHoverableContent={true}>
  <!-- Will have a delayDuration of 0 and disableHoverableContent of true -->
  <Tooltip.Root>
    <Tooltip.Trigger />
    <Tooltip.Portal>
      <Tooltip.Content>
        <Tooltip.Arrow />
      </Tooltip.Content>
    </Tooltip.Portal>
  </Tooltip.Root>
  <!-- Will have a delayDuration of 0 and disableHoverableContent of true -->
  <Tooltip.Root>
    <Tooltip.Trigger />
    <Tooltip.Portal>
      <Tooltip.Content>
        <Tooltip.Arrow />
      </Tooltip.Content>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>

It also ensures that only a single tooltip within the same provider can be open at a time. It's recommended to wrap your root layout content with the provider component, setting your sensible default props there.

+layout.svelte
	<script lang="ts">
  import { Tooltip } from "bits-ui";
  let { children } = $props();
</script>
 
<Tooltip.Provider>
  {@render children()}
</Tooltip.Provider>

Managing Open State

This section covers how to manage the open state of the component.

Two-Way Binding

Use bind:open for simple, automatic state synchronization:

	<script lang="ts">
  import { Tooltip } from "bits-ui";
  let isOpen = $state(false);
</script>
 
<button onclick={() => (isOpen = true)}>Open Tooltip</button>
 
<Tooltip.Root bind:open={isOpen}>
  <!-- ... -->
</Tooltip.Root>

Fully Controlled

Use a Function Binding for complete control over the state's reads and writes.

	<script lang="ts">
  import { Tooltip } from "bits-ui";
  let myOpen = $state(false);
 
  function getOpen() {
    return myOpen;
  }
 
  function setOpen(newOpen: boolean) {
    myOpen = newOpen;
  }
</script>
 
<Tooltip.Root bind:open={getOpen, setOpen}>
  <!-- ... -->
</Tooltip.Root>

Mobile Tooltips

Tooltips are not supported on mobile devices. The intent of a tooltip is to provide a "tip" about a "tool" before the user interacts with that tool (in most cases, a button).

This is not possible on mobile devices, because there is no sense of hover on mobile. If a user were to press/touch a button with a tooltip, the action that button triggers would be taken before they were even able to see the tooltip, which renders it useless.

If you are using a tooltip on a button without an action, you should consider using a Popover instead.

If you'd like to learn more about how we came to this decision, here are some useful resources:

The tooltip is not the appropriate role for the more information "i" icon, ⓘ. A tooltip is directly associated with the owning element. The ⓘ isn't 'described by' detailed information; the tool or control is.

MDN ARIA Tooltips


Tooltips should only ever contain non-essential content. The best approach to writing tooltip content is to always assume it may never be read.

Tooltips in the time of WCAG 2.1

Reusable Components

It's recommended to use the Tooltip primitives to build your own custom tooltip component that can be used throughout your application.

Below is an example of how you might create a reusable tooltip component that can be used throughout your application. Of course, this isn't the only way to do it, but it should give you a good idea of how to compose the primitives.

MyTooltip.svelte
	<script lang="ts">
  import { Tooltip } from "bits-ui";
  import { type Snippet } from "svelte";
 
  type Props = Tooltip.RootProps & {
    trigger: Snippet;
    triggerProps?: Tooltip.TriggerProps;
  };
 
  let {
    open = $bindable(false),
    children,
    buttonText,
    triggerProps = {},
    ...restProps
  }: Tooltip.RootProps = $props();
</script>
 
<!--
 Ensure you have a `Tooltip.Provider` component wrapping
 your root layout content
-->
<Tooltip.Root bind:open {onOpenChange}>
  <Tooltip.Trigger {...triggerProps}>
    {@render trigger()}
  </Tooltip.Trigger>
  <Tooltip.Portal>
    <Tooltip.Content>
      <Tooltip.Arrow />
      {@render children?.()}
    </Tooltip.Content>
  </Tooltip.Portal>
</Tooltip.Root>

You could then use the MyTooltip component in your application like so:

+page.svelte
	<script lang="ts">
  import MyTooltip from "$lib/components/MyTooltip.svelte";
  import BoldIcon from "..some-icon-library"; // not real
</script>
 
<MyTooltip triggerProps={{ onclick: () => alert("changed to bold!") }}>
  {#snippet trigger()}
    <BoldIcon />
  {/snippet}
  Change font to bold
</MyTooltip>

Delay Duration

You can change how long a user needs to hover over a trigger before the tooltip appears by setting the delayDuration prop on the Tooltip.Root or Tooltip.Provider component.

	<Tooltip.Root delayDuration={200}>
  <!-- .... -->
</Tooltip.Root>
delayDuration=200
delayDuration=1000
delayDuration=2500

Close on Trigger Click

By default, the tooltip will close when the user clicks the trigger. If you want to disable this behavior, you can set the disableCloseOnTriggerClick prop to true.

	<Tooltip.Root disableCloseOnTriggerClick>
  <!-- .... -->
</Tooltip.Root>

Hoverable Content

By default, the tooltip will remain open when the user hovers over the content. If you instead want the tooltip to close as the user moves their mouse towards the content, you can set the disableHoverableContent prop to true.

	<Tooltip.Root disableHoverableContent>
  <!-- .... -->
</Tooltip.Root>

Non-Keyboard Focus

If you want to prevent opening the tooltip when the user focuses the trigger without using the keyboard, you can set the ignoreNonKeyboardFocus prop to true.

	<Tooltip.Root ignoreNonKeyboardFocus>
  <!-- .... -->
</Tooltip.Root>

Svelte Transitions

You can use the forceMount prop along with the child snippet to forcefully mount the Tooltip.Content component to use Svelte Transitions or another animation library that requires more control.

	<script lang="ts">
	import { Tooltip } from "bits-ui";
	import { fly, fade } from "svelte/transition";
</script>
 
<Tooltip.Root>
	<!-- ... other tooltip components -->
	<Tooltip.Content forceMount>
		{#snippet child({ wrapperProps, props, open })}
			{#if open}
				<div {...wrapperProps}>
					<div {...props} transition:fly>
						<!-- ... -->
					</div>
				</div>
			{/if}
		{/snippet}
	</Tooltip.Content>
</Dialog.Root>

Of course, this isn't the prettiest syntax, so it's recommended to create your own reusable content components that handles this logic if you intend to use this approach throughout your app. For more information on using transitions with Bits UI components, see the Transitions documentation.

	<script lang="ts">
  import { Tooltip } from "bits-ui";
  import MagicWand from "phosphor-svelte/lib/MagicWand";
  import { fly } from "svelte/transition";
</script>
 
<Tooltip.Provider>
  <Tooltip.Root delayDuration={200}>
    <Tooltip.Trigger
      class="border-border-input bg-background-alt shadow-btn ring-dark ring-offset-background
		hover:bg-muted focus-visible:ring-dark focus-visible:ring-offset-background focus-visible:outline-hidden inline-flex size-10 items-center justify-center rounded-full border focus-visible:ring-2 focus-visible:ring-offset-2"
    >
      <MagicWand class="size-5" />
    </Tooltip.Trigger>
    <Tooltip.Content sideOffset={8} forceMount>
      {#snippet child({ wrapperProps, props, open })}
        {#if open}
          <div {...wrapperProps}>
            <div {...props} transition:fly={{ duration: 300 }}>
              <div
                class="rounded-input border-dark-10 bg-background shadow-popover outline-hidden z-0 flex items-center justify-center border p-3 text-sm font-medium"
              >
                Make some magic!
              </div>
            </div>
          </div>
        {/if}
      {/snippet}
    </Tooltip.Content>
  </Tooltip.Root>
</Tooltip.Provider>

Opt-out of Floating UI

When you use the Tooltip.Content component, Bits UI uses Floating UI to position the content relative to the trigger, similar to other popover-like components.

You can opt-out of this behavior by instead using the Tooltip.ContentStatic component. This component does not use Floating UI and leaves positioning the content entirely up to you.

	<Tooltip.Root>
  <Tooltip.Trigger>Hello</Tooltip.Trigger>
  <Tooltip.ContentStatic>
    <!-- ... -->
  </Tooltip.ContentStatic>
</Tooltip.Root>

Custom Anchor

By default, the Tooltip.Content is anchored to the Tooltip.Trigger component, which determines where the content is positioned.

If you wish to instead anchor the content to a different element, you can pass either a selector string or an HTMLElement to the customAnchor prop of the Tooltip.Content component.

	<script lang="ts">
  import { Tooltip } from "bits-ui";
  let customAnchor = $state<HTMLElement>(null!);
</script>
 
<div bind:this={customAnchor}></div>
 
<Tooltip.Root>
  <Tooltip.Trigger />
  <Tooltip.Content {customAnchor}>
    <!-- ... -->
  </Tooltip.Content>
</Tooltip.Root>
Custom Anchor
	<script lang="ts">
  import { Tooltip } from "bits-ui";
  import MagicWand from "phosphor-svelte/lib/MagicWand";
 
  let customAnchor = $state<HTMLElement | null>(null);
</script>
 
<div class="flex items-center gap-6">
  <div class="rounded-md border p-3" bind:this={customAnchor}>
    Custom Anchor
  </div>
  <Tooltip.Provider>
    <Tooltip.Root delayDuration={200}>
      <Tooltip.Trigger
        class="border-border-input bg-background-alt shadow-btn ring-dark ring-offset-background
			hover:bg-muted focus-visible:ring-dark focus-visible:ring-offset-background focus-visible:outline-hidden inline-flex size-10 items-center justify-center rounded-full border focus-visible:ring-2 focus-visible:ring-offset-2"
      >
        <MagicWand class="size-5" />
      </Tooltip.Trigger>
      <Tooltip.Content sideOffset={8} {customAnchor}>
        <div
          class="rounded-input border-dark-10 bg-background shadow-popover outline-hidden z-0 flex items-center justify-center border p-3 text-sm font-medium"
        >
          Make some magic!
        </div>
      </Tooltip.Content>
    </Tooltip.Root>
  </Tooltip.Provider>
</div>

API Reference

Tooltip.Provider

A provider component which contains shared state and logic for the tooltips within its subtree.

Property Details
delayDuration
disableHoverableContent
disabled
disableCloseOnTriggerClick
skipDelayDuration
ignoreNonKeyboardFocus
children
Data Attribute Details

Tooltip.Root

The root component containing the parts of the tooltip. Must be a descendant of a Tooltip.Provider component.

Property Details
open
onOpenChange
onOpenChangeComplete
disabled
delayDuration
disableHoverableContent
disableCloseOnTriggerClick
ignoreNonKeyboardFocus
children
Data Attribute Details

Tooltip.Trigger

A component which triggers the opening and closing of the tooltip on hover or focus.

Property Details
disabled
ref
children
child
Data Attribute Details
data-state
data-tooltip-trigger

Tooltip.Content

The contents of the tooltip which are displayed when the tooltip is open.

Property Details
side
sideOffset
align
alignOffset
arrowPadding
avoidCollisions
collisionBoundary
collisionPadding
sticky
hideWhenDetached
updatePositionStrategy
strategy
preventScroll
customAnchor
onInteractOutside
onFocusOutside
interactOutsideBehavior
onEscapeKeydown
escapeKeydownBehavior
forceMount
dir
ref
children
child
Data Attribute Details
data-state
data-tooltip-content
CSS Variable Details
--bits-tooltip-content-transform-origin
--bits-tooltip-content-available-width
--bits-tooltip-content-available-height
--bits-tooltip-anchor-width
--bits-tooltip-anchor-height

Tooltip.ContentStatic

The contents of the tooltip which are displayed when the tooltip is open. (Static/No Floating UI)

Property Details
onInteractOutside
onFocusOutside
interactOutsideBehavior
onEscapeKeydown
escapeKeydownBehavior
forceMount
dir
ref
children
child
Data Attribute Details
data-state
data-tooltip-content

Tooltip.Arrow

An optional arrow element which points to the trigger when the tooltip is open.

Property Details
width
height
ref
children
child
Data Attribute Details
data-arrow
data-tooltip-arrow
data-side

Tooltip.Portal

When used, will render the tooltip content into the body or custom to element when open

Property Details
to
disabled
children
Data Attribute Details