ShardsUI is in beta. APIs may change before 1.0.

Tooltip

A hover or focus hint.

<script lang="ts">
  import { Tooltip } from '@shardsui/svelte/tooltip'

  const actions = [
    { label: 'Bookmark', tip: 'Save for later', icon: bookmarkIcon },
    { label: 'Transcript', tip: 'Read along', icon: captionsIcon },
    { label: 'Speed', tip: 'Change playback speed', icon: gaugeIcon }
  ]
</script>

<Tooltip.Provider>
  <div class="flex">
    {#each actions as action (action.label)}
      <Tooltip.Root>
        <Tooltip.Trigger
          class="flex size-8 items-center justify-center border border-gray-200 bg-gray-50 text-sm font-normal text-gray-900 select-none not-first:border-l-0 first:rounded-l-md last:rounded-r-md hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 data-popup-open:bg-gray-100"
          aria-label={action.label}
        >
          {@render action.icon()}
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Positioner sideOffset={10}>
            <Tooltip.Popup
              class="origin-(--transform-origin) rounded-md bg-gray-50 px-2 py-1 text-sm text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[transform,scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-instant:transition-none data-starting-style:scale-95 data-starting-style:opacity-0"
            >
              <Tooltip.Arrow
                class="data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
              >
                {@render arrow()}
              </Tooltip.Arrow>
              {action.tip}
            </Tooltip.Popup>
          </Tooltip.Positioner>
        </Tooltip.Portal>
      </Tooltip.Root>
    {/each}
  </div>
</Tooltip.Provider>

{#snippet bookmarkIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path
      d="M19.25 20.2515V5.75C19.25 4.09315 17.9069 2.75 16.25 2.75H7.75C6.09315 2.75 4.75 4.09315 4.75 5.75V20.2515C4.75 21.0522 5.64414 21.5281 6.30839 21.081L10.3248 18.3776C11.3376 17.6959 12.6624 17.6959 13.6752 18.3776L17.6916 21.081C18.3559 21.5282 19.25 21.0522 19.25 20.2515Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet captionsIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path
      fill-rule="evenodd"
      clip-rule="evenodd"
      d="M10.2997 10.5993C9.61468 10.084 8.63619 10.1389 8.01256 10.7625C7.32915 11.4459 7.32915 12.554 8.01256 13.2374C8.63619 13.861 9.61468 13.9159 10.2997 13.4006C10.6307 13.1516 11.1009 13.2181 11.3499 13.5491C11.5989 13.8801 11.5324 14.3503 11.2014 14.5993C9.92761 15.5575 8.11135 15.4575 6.9519 14.2981C5.6827 13.0289 5.6827 10.9711 6.9519 9.70186C8.11135 8.54241 9.92761 8.44244 11.2014 9.4006C11.5324 9.6496 11.5989 10.1198 11.3499 10.4508C11.1009 10.7818 10.6307 10.8483 10.2997 10.5993ZM16.7997 10.5993C16.1147 10.084 15.1362 10.1389 14.5126 10.7625C13.8291 11.4459 13.8291 12.554 14.5126 13.2374C15.1362 13.861 16.1147 13.9159 16.7997 13.4006C17.1307 13.1516 17.6009 13.2181 17.8499 13.5491C18.0989 13.8801 18.0324 14.3503 17.7014 14.5993C16.4276 15.5575 14.6114 15.4575 13.4519 14.2981C12.1827 13.0289 12.1827 10.9711 13.4519 9.70186C14.6114 8.54241 16.4276 8.44244 17.7014 9.4006C18.0324 9.6496 18.0989 10.1198 17.8499 10.4508C17.6009 10.7818 17.1307 10.8483 16.7997 10.5993Z"
      fill="currentColor"
    />
    <path
      d="M17.25 3.75H6.75C5.09315 3.75 3.75 5.09315 3.75 6.75V17.25C3.75 18.9069 5.09315 20.25 6.75 20.25H17.25C18.9069 20.25 20.25 18.9069 20.25 17.25V6.75C20.25 5.09315 18.9069 3.75 17.25 3.75Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="square"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet gaugeIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path
      d="M10 10L5.5 5.5M12 5.25V2.75C17.1086 2.75 21.25 6.89137 21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 10.5672 3.07575 9.21057 3.65722 8M14.75 12C14.75 13.5188 13.5188 14.75 12 14.75C10.4812 14.75 9.25 13.5188 9.25 12C9.25 10.4812 10.4812 9.25 12 9.25C13.5188 9.25 14.75 10.4812 14.75 12Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet arrow()}
  <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
    <path
      d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
      class="fill-gray-50"
    />
    <path
      d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
      class="fill-gray-200"
    />
    <path
      d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
    />
  </svg>
{/snippet}

Anatomy

<script>
  import { Tooltip } from '@shardsui/svelte/tooltip'
</script>

<Tooltip.Provider>
  <Tooltip.Root>
    <Tooltip.Trigger />
    <Tooltip.Portal>
      <Tooltip.Positioner>
        <Tooltip.Popup>
          <Tooltip.Viewport>
            <!-- content -->
          </Tooltip.Viewport>
          <Tooltip.Arrow />
        </Tooltip.Popup>
      </Tooltip.Positioner>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>

Usage guidelines

  • Prefer using tooltips as visual labels only: alone they are not accessible to touch or screen reader users.
  • Provide an accessible name for the trigger: a tooltip is visual-only and doesn't label its trigger. Give the trigger an aria-label that closely matches the tooltip's content so screen reader users get a consistent name.

Alternatives to tooltips

Without a hover-capable pointer, there's no discoverable way to surface a tooltip before tapping its trigger. Long press isn't a way out either: iOS has no system-standard tooltip affordance, and Android's long-press gesture is already claimed by the browser's contextual menus. So tooltips stay disabled on touch devices.

Infotips

For content that opens when hovering an info icon, use Popover with openOnHover on the trigger instead; that keeps the content reachable for touch and screen reader users. The test: when opening the overlay is the trigger's whole job, it's a popover; when the trigger does something else and the overlay is incidental, it's a tooltip.

Description text

When a description is essential to understanding an element, keep it out of a tooltip: put it in inline text, or in a Popover when space is tight, so everyone can read it. Save tooltips for non-essential hints, and make icon-only triggers legible on their own, especially on mobile where the tooltip's text label never shows.

Contextual feedback messages

For feedback tied to a specific control, use Toast and its anchoring — it announces the message to screen readers and handles richer content.

Examples

Detached triggers

The trigger usually lives inside <Tooltip.Root>, next to the hint it shows, as in the hero demo above.

When the two can't share a spot in the markup, render <Tooltip.Trigger> wherever the element belongs and connect it to the root through a shared handle from new Tooltip.Handle().

The handle's imperative methods, open() and close(), need a <Tooltip.Root> using the same handle to be mounted — calls made while no root is attached (before one mounts, or after it unmounts) are ignored and not replayed. Each root starts from fresh state when it mounts; no open state carries over from a previous mount.

<script>
  const demoTooltip = new Tooltip.Handle()
</script>

<Tooltip.Trigger handle={demoTooltip}>Button</Tooltip.Trigger>

<Tooltip.Root handle={demoTooltip}>...</Tooltip.Root>
<script lang="ts">
  import { Tooltip } from '@shardsui/svelte/tooltip'

  const bookmarkTooltip = new Tooltip.Handle()
</script>

<Tooltip.Provider>
  <Tooltip.Trigger
    class="flex size-8 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-sm font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 data-popup-open:bg-gray-100"
    handle={bookmarkTooltip}
    aria-label="Bookmark"
  >
    {@render bookmarkIcon()}
  </Tooltip.Trigger>

  <Tooltip.Root handle={bookmarkTooltip}>
    <Tooltip.Portal>
      <Tooltip.Positioner sideOffset={10}>
        <Tooltip.Popup
          class="origin-(--transform-origin) rounded-md bg-gray-50 px-2 py-1 text-sm text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[transform,scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-instant:transition-none data-starting-style:scale-95 data-starting-style:opacity-0"
        >
          <Tooltip.Arrow
            class="data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
          >
            {@render arrow()}
          </Tooltip.Arrow>
          Save for later
        </Tooltip.Popup>
      </Tooltip.Positioner>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>

{#snippet bookmarkIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path
      d="M19.25 20.2515V5.75C19.25 4.09315 17.9069 2.75 16.25 2.75H7.75C6.09315 2.75 4.75 4.09315 4.75 5.75V20.2515C4.75 21.0522 5.64414 21.5281 6.30839 21.081L10.3248 18.3776C11.3376 17.6959 12.6624 17.6959 13.6752 18.3776L17.6916 21.081C18.3559 21.5282 19.25 21.0522 19.25 20.2515Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet arrow()}
  <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
    <path
      d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
      class="fill-gray-50"
    />
    <path
      d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
      class="fill-gray-200"
    />
    <path
      d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
    />
  </svg>
{/snippet}

Multiple triggers

One tooltip can back several triggers. Give the same handle to a set of detached triggers, or list multiple <Tooltip.Trigger> elements inside a single <Tooltip.Root>.

<Tooltip.Root>
  <Tooltip.Trigger>Trigger 1</Tooltip.Trigger>
  <Tooltip.Trigger>Trigger 2</Tooltip.Trigger>
  ...
</Tooltip.Root>

Each trigger can pass its own payload, letting one tooltip say something different per trigger. The children snippet on <Tooltip.Root> receives the active trigger's payload; add a type argument to new Tooltip.Handle() to type it:

<script>
  const demoTooltip = new Tooltip.Handle<{ text: string }>() 
</script>

<Tooltip.Trigger handle={demoTooltip} payload={{ text: 'Trigger 1' }}>Trigger 1</Tooltip.Trigger>
<Tooltip.Trigger handle={demoTooltip} payload={{ text: 'Trigger 2' }}>Trigger 2</Tooltip.Trigger>

<Tooltip.Root handle={demoTooltip}>
  {#snippet children({ payload })}
    <Tooltip.Portal>
      <Tooltip.Positioner sideOffset={8}>
        <Tooltip.Popup>
          {#if payload}<span>Opened by {payload.text}</span>{/if}
        </Tooltip.Popup>
      </Tooltip.Positioner>
    </Tooltip.Portal>
  {/snippet}
</Tooltip.Root>

Controlled mode with multiple triggers

Drive the open state yourself with bind:open and onOpenChange on <Tooltip.Root>. Across multiple triggers, give each <Tooltip.Trigger> an id and add bind:triggerId to <Tooltip.Root>: each trigger publishes its own id when it opens the tooltip, and setting triggerId yourself anchors the tooltip to that trigger. Pass triggerId one-way instead if you want to drive it entirely from your own state.

<script lang="ts">
  import { Tooltip } from '@shardsui/svelte/tooltip'

  const layoutTooltip = new Tooltip.Handle()

  const views = [
    { id: 'view-grid', label: 'Grid', icon: gridIcon },
    { id: 'view-list', label: 'List', icon: listIcon },
    { id: 'view-columns', label: 'Columns', icon: columnsIcon }
  ]

  let open = $state(false)
  let triggerId = $state<string | null>(null)

  function revealList() {
    triggerId = 'view-list'
    open = true
  }
</script>

<Tooltip.Provider>
  <div class="flex flex-wrap items-center justify-center gap-3">
    <div class="flex">
      {#each views as view (view.id)}
        <Tooltip.Trigger
          class="flex size-8 items-center justify-center border border-gray-200 bg-gray-50 text-sm font-normal text-gray-900 select-none not-first:border-l-0 first:rounded-l-md last:rounded-r-md hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 data-popup-open:bg-gray-100"
          handle={layoutTooltip}
          id={view.id}
          aria-label={view.label}
        >
          {@render view.icon()}
        </Tooltip.Trigger>
      {/each}
    </div>

    <button
      type="button"
      class="flex h-8 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3 text-sm font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100"
      onclick={revealList}
    >
      Reveal list tooltip
    </button>
  </div>

  <Tooltip.Root handle={layoutTooltip} bind:open bind:triggerId>
    <Tooltip.Portal>
      <Tooltip.Positioner sideOffset={10} class="max-w-(--available-width)">
        <Tooltip.Popup
          class="origin-(--transform-origin) rounded-md bg-gray-50 px-2 py-1 text-sm text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[transform,scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-instant:transition-none data-starting-style:scale-95 data-starting-style:opacity-0"
        >
          <Tooltip.Arrow
            class="data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
          >
            {@render arrow()}
          </Tooltip.Arrow>
          Change layout
        </Tooltip.Popup>
      </Tooltip.Positioner>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>

{#snippet gridIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path
      d="M3.75 8.55C3.75 6.86984 3.75 6.02976 4.07698 5.38803C4.3646 4.82354 4.82354 4.3646 5.38803 4.07698C6.02976 3.75 6.86984 3.75 8.55 3.75H10.25V10.25H3.75V8.55Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M13.75 3.75H15.45C17.1302 3.75 17.9702 3.75 18.612 4.07698C19.1765 4.3646 19.6354 4.82354 19.923 5.38803C20.25 6.02976 20.25 6.86984 20.25 8.55V10.25H13.75V3.75Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M3.75 13.75H10.25V20.25H8.55C6.86984 20.25 6.02976 20.25 5.38803 19.923C4.82354 19.6354 4.3646 19.1765 4.07698 18.612C3.75 17.9702 3.75 17.1302 3.75 15.45V13.75Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M13.75 13.75H20.25V15.45C20.25 17.1302 20.25 17.9702 19.923 18.612C19.6354 19.1765 19.1765 19.6354 18.612 19.923C17.9702 20.25 17.1302 20.25 15.45 20.25H13.75V13.75Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet listIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path d="M8.75 6L20.25 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
    <circle cx="4.2" cy="5.9998" r="1.2" fill="currentColor" />
    <path d="M8.75 12L20.25 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
    <circle cx="4.2" cy="11.9998" r="1.2" fill="currentColor" />
    <path d="M8.75 18L20.25 18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
    <circle cx="4.2" cy="17.9998" r="1.2" fill="currentColor" />
  </svg>
{/snippet}

{#snippet columnsIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path
      d="M17.25 3.75H6.75C5.09315 3.75 3.75 5.09315 3.75 6.75V17.25C3.75 18.9069 5.09315 20.25 6.75 20.25H17.25C18.9069 20.25 20.25 18.9069 20.25 17.25V6.75C20.25 5.09315 18.9069 3.75 17.25 3.75Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M9.25 4V20"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M14.75 4V20"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet arrow()}
  <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
    <path
      d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
      class="fill-gray-50"
    />
    <path
      d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
      class="fill-gray-200"
    />
    <path
      d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
    />
  </svg>
{/snippet}

Animating the Tooltip

A tooltip can travel smoothly from one trigger to the next instead of blinking off and on. Position, size, and content animate independently.

Position and Size

Position sits on the Positioner: transition left, right, top, and bottom. Size sits on the Popup: transition width and height.

Content

The content can shift too when triggers carry different hints. Wrap it in <Tooltip.Viewport>, which spots the trigger change and writes a data-activation-direction attribute — a space-separated horizontal and vertical pair such as right down, left, or up — indicating where the new trigger falls relative to the old one. Match a single token with the ~= attribute selector, such as [data-activation-direction~='right'].

Mid-transition, the viewport keeps both hints mounted, each in its own wrapper:

  • data-current: the entering content, or the sole content when nothing is transitioning. It also carries data-starting-style while it animates in.
  • data-previous: the leaving content during a transition. It also carries data-ending-style while it animates out.

Target these to write the enter and exit animations.

<script lang="ts">
  import { Tooltip } from '@shardsui/svelte/tooltip'

  const layoutTooltip = new Tooltip.Handle<string>()

  const views = [
    { id: 'view-grid', label: 'Grid', text: 'Grid view', icon: gridIcon },
    { id: 'view-list', label: 'List', text: 'List view', icon: listIcon },
    { id: 'view-columns', label: 'Columns', text: 'Columns view', icon: columnsIcon }
  ]
</script>

<Tooltip.Provider>
  <div class="flex">
    {#each views as view (view.id)}
      <Tooltip.Trigger
        class="flex size-8 items-center justify-center border border-gray-200 bg-gray-50 text-gray-900 select-none not-first:border-l-0 first:rounded-l-md last:rounded-r-md hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 data-popup-open:bg-gray-100"
        handle={layoutTooltip}
        id={view.id}
        payload={view.text}
        aria-label={view.label}
      >
        {@render view.icon()}
      </Tooltip.Trigger>
    {/each}
  </div>

  <Tooltip.Root handle={layoutTooltip}>
    {#snippet children({ payload })}
      <Tooltip.Portal>
        <Tooltip.Positioner
          sideOffset={10}
          class="h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] duration-350 ease-out-quint data-instant:transition-none"
        >
          <Tooltip.Popup
            class="relative h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-md bg-gray-50 text-sm text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[width,height,opacity,scale] duration-350 ease-out-quint data-ending-style:scale-95 data-ending-style:opacity-0 data-instant:transition-none data-starting-style:scale-95 data-starting-style:opacity-0"
          >
            <Tooltip.Arrow
              class="transition-[left] duration-350 ease-out-quint data-instant:transition-none data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
            >
              {@render arrow()}
            </Tooltip.Arrow>

            <Tooltip.Viewport
              class="relative size-full overflow-clip px-(--viewport-inline-padding) py-1 [--viewport-inline-padding:0.5rem] **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding))] **:data-current:translate-x-0 **:data-current:opacity-100 **:data-current:transition-[translate,opacity] **:data-current:duration-[350ms,175ms] **:data-current:ease-out-quint **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding))] **:data-previous:translate-x-0 **:data-previous:opacity-100 **:data-previous:transition-[translate,opacity] **:data-previous:duration-[350ms,175ms] **:data-previous:ease-out-quint data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:-translate-x-1/2 data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:opacity-0 data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:translate-x-1/2 data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:opacity-0 data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:translate-x-1/2 data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:opacity-0 data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:-translate-x-1/2 data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:opacity-0 [[data-instant]_&_[data-current]]:transition-none [[data-instant]_&_[data-previous]]:transition-none"
            >
              <span class="whitespace-nowrap">{payload}</span>
            </Tooltip.Viewport>
          </Tooltip.Popup>
        </Tooltip.Positioner>
      </Tooltip.Portal>
    {/snippet}
  </Tooltip.Root>
</Tooltip.Provider>

{#snippet gridIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path
      d="M3.75 8.55C3.75 6.86984 3.75 6.02976 4.07698 5.38803C4.3646 4.82354 4.82354 4.3646 5.38803 4.07698C6.02976 3.75 6.86984 3.75 8.55 3.75H10.25V10.25H3.75V8.55Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M13.75 3.75H15.45C17.1302 3.75 17.9702 3.75 18.612 4.07698C19.1765 4.3646 19.6354 4.82354 19.923 5.38803C20.25 6.02976 20.25 6.86984 20.25 8.55V10.25H13.75V3.75Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M3.75 13.75H10.25V20.25H8.55C6.86984 20.25 6.02976 20.25 5.38803 19.923C4.82354 19.6354 4.3646 19.1765 4.07698 18.612C3.75 17.9702 3.75 17.1302 3.75 15.45V13.75Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M13.75 13.75H20.25V15.45C20.25 17.1302 20.25 17.9702 19.923 18.612C19.6354 19.1765 19.1765 19.6354 18.612 19.923C17.9702 20.25 17.1302 20.25 15.45 20.25H13.75V13.75Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet listIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path d="M8.75 6L20.25 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
    <circle cx="4.2" cy="5.9998" r="1.2" fill="currentColor" />
    <path d="M8.75 12L20.25 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
    <circle cx="4.2" cy="11.9998" r="1.2" fill="currentColor" />
    <path d="M8.75 18L20.25 18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
    <circle cx="4.2" cy="17.9998" r="1.2" fill="currentColor" />
  </svg>
{/snippet}

{#snippet columnsIcon()}
  <svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
    <path
      d="M17.25 3.75H6.75C5.09315 3.75 3.75 5.09315 3.75 6.75V17.25C3.75 18.9069 5.09315 20.25 6.75 20.25H17.25C18.9069 20.25 20.25 18.9069 20.25 17.25V6.75C20.25 5.09315 18.9069 3.75 17.25 3.75Z"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M9.25 4V20"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M14.75 4V20"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet arrow()}
  <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
    <path
      d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
      class="fill-gray-50"
    />
    <path
      d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
      class="fill-gray-200"
    />
    <path
      d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
    />
  </svg>
{/snippet}

API reference

Provider

Provides a shared delay for multiple tooltips. Once one tooltip is open, siblings open with no delay. Doesn't render its own HTML element.

PropTypeDefault

Root

Groups all parts of the tooltip. Doesn't render its own HTML element.

PropTypeDefault

Trigger

An element to attach the tooltip to. Renders a <button> element.

PropTypeDefault
AttributeDescription
data-popup-openPresent when the corresponding tooltip is open.
data-trigger-disabledPresent when the trigger is disabled.

Portal

A portal that moves the popup out to <body>, clear of ancestor clipping and stacking. Renders a <div> element.

PropTypeDefault

Positioner

Positions the tooltip against the trigger. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-anchor-hiddenPresent when the anchor is hidden.
data-instantPresent when animations should be instant. Value is 'tracking-cursor' while trackCursorAxis is set, otherwise 'focus', 'dismiss', or 'delay'.
CSS VariableDescription
--available-widthAvailable width between the anchor and the viewport edge.
--available-heightAvailable height between the anchor and the viewport edge.
--anchor-widthWidth of the anchor element.
--anchor-heightHeight of the anchor element.
--transform-originTransform origin for scale animations.
--positioner-widthThe positioner element's width. Set while a <Tooltip.Viewport> morphs content.
--positioner-heightThe positioner element's height. Set while a <Tooltip.Viewport> morphs content.

Popup

A container for the tooltip contents. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the tooltip is open.
data-closedPresent when the tooltip is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-starting-stylePresent when the tooltip is animating in.
data-ending-stylePresent when the tooltip is animating out.
data-instantPresent when animations should be instant.
CSS VariableDescription
--popup-widthThe popup element's width. Set while a <Tooltip.Viewport> morphs content.
--popup-heightThe popup element's height. Set while a <Tooltip.Viewport> morphs content.

Arrow

Displays an element positioned against the anchor. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-uncenteredPresent when the arrow cannot be centered.
data-instantPresent when animations should be instant.

Viewport

A viewport for displaying content transitions. This component is only required if one popup can be opened by multiple triggers, its content changes based on the trigger, and switching between them is animated. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-activation-directionDirection the popup was activated from. A horizontal and a vertical token separated by a space, e.g. right down; either can be empty, so match one with [data-activation-direction~='right'].
data-transitioningPresent while a content transition is in progress.
data-currentApplied to the wrapper of the current content.
data-previousApplied to the wrapper of the outgoing content, present only during a transition.
data-instantPresent when animations should be instant.
data-starting-styleApplied to the data-current wrapper while the incoming content is animating in.
data-ending-styleApplied to the data-previous wrapper while the outgoing content is animating out.
CSS VariableDescription
--popup-widthWidth of the outgoing popup content, set on the data-previous container. Use it to freeze the popup size while the content morphs.
--popup-heightHeight of the outgoing popup content, set on the data-previous container.

Reach for the Viewport only when a single popup is opened by multiple triggers, its content differs per trigger, and the switch between them is animated. When you do, set width: var(--positioner-width) and height: var(--positioner-height) on the Positioner so its box is frozen to the measured size during the transition; otherwise content-driven resizing can make the popup thrash or flip to another side.

Handle

Connects a <Tooltip.Root> with detached <Tooltip.Trigger> components, and controls the tooltip imperatively. Pass a type argument to type the payload.

const tooltip = new Tooltip.Handle<Payload>()
MemberType