Tooltip

Contextual overlay primitive that links descriptive content to a trigger element.

Target binding

The for property connects Tooltip to the element that receives its description.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-tooltip/ui-tooltip.html"
>

<ui-button
  id="tooltip-trigger"
>Help</ui-button>
<ui-tooltip
  for="tooltip-trigger"
>Tooltip content.</ui-tooltip>
<script setup>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

<template>
  <UiButton
    id="tooltip-trigger"
  >Help</UiButton>
  <UiTooltip
    for="tooltip-trigger"
  >Tooltip content.</UiTooltip>
</template>
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="tooltip-trigger"
    >Help</UiButton>
    <UiTooltip
      htmlFor="tooltip-trigger"
    >Tooltip content.</UiTooltip>
  </>;
}
/** @jsxImportSource preact */
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="tooltip-trigger"
    >Help</UiButton>
    <UiTooltip
      htmlFor="tooltip-trigger"
    >Tooltip content.</UiTooltip>
  </>;
}
<script>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

<UiButton
  id="tooltip-trigger"
>Help</UiButton>
<UiTooltip
  for="tooltip-trigger"
>Tooltip content.</UiTooltip>
import { Component } from "@angular/core";
import { UiButton, UiTooltip } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiButton, UiTooltip],
  template: `
    <ng-template
      ui-button
      [hnAttributes]="{ id: 'tooltip-trigger' }"
    >Help</ng-template>
    <span
      ui-tooltip
      [for]="'tooltip-trigger'"
    >Tooltip content.</span>
  `,
})
export default class Example {}

Placement and delays

Placement chooses the preferred edge; show-delay and hide-delay tune hover timing.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-tooltip/ui-tooltip.html"
>

<ui-button
  id="tooltip-timed-trigger"
>Help</ui-button>
<ui-tooltip
  for="tooltip-timed-trigger"
  placement="bottom-start"
  show-delay="0"
  hide-delay="200"
>Tooltip content.</ui-tooltip>
<script setup>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

<template>
  <UiButton
    id="tooltip-timed-trigger"
  >Help</UiButton>
  <UiTooltip
    for="tooltip-timed-trigger"
    placement="bottom-start"
    :show-delay="0"
    :hide-delay="200"
  >Tooltip content.</UiTooltip>
</template>
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="tooltip-timed-trigger"
    >Help</UiButton>
    <UiTooltip
      htmlFor="tooltip-timed-trigger"
      placement="bottom-start"
      showDelay={0}
      hideDelay={200}
    >Tooltip content.</UiTooltip>
  </>;
}
/** @jsxImportSource preact */
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="tooltip-timed-trigger"
    >Help</UiButton>
    <UiTooltip
      htmlFor="tooltip-timed-trigger"
      placement="bottom-start"
      showDelay={0}
      hideDelay={200}
    >Tooltip content.</UiTooltip>
  </>;
}
<script>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

<UiButton
  id="tooltip-timed-trigger"
>Help</UiButton>
<UiTooltip
  for="tooltip-timed-trigger"
  placement="bottom-start"
  showDelay={0}
  hideDelay={200}
>Tooltip content.</UiTooltip>
import { Component } from "@angular/core";
import { UiButton, UiTooltip } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiButton, UiTooltip],
  template: `
    <ng-template
      ui-button
      [hnAttributes]="{ id: 'tooltip-timed-trigger' }"
    >Help</ng-template>
    <span
      ui-tooltip
      [for]="'tooltip-timed-trigger'"
      [placement]="'bottom-start'"
      [showDelay]="0"
      [hideDelay]="200"
    >Tooltip content.</span>
  `,
})
export default class Example {}

inverse

Inverse uses the high-contrast surface: dark on light themes and light on dark themes.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-tooltip/ui-tooltip.html"
>

<ui-button
  id="tooltip-inverse-trigger"
>Shortcut</ui-button>
<ui-tooltip
  for="tooltip-inverse-trigger"
  inverse
  show-delay="0"
>Duplicate (⌘D)</ui-tooltip>
<script setup>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

<template>
  <UiButton
    id="tooltip-inverse-trigger"
  >Shortcut</UiButton>
  <UiTooltip
    for="tooltip-inverse-trigger"
    :inverse="true"
    :show-delay="0"
  >Duplicate (⌘D)</UiTooltip>
</template>
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="tooltip-inverse-trigger"
    >Shortcut</UiButton>
    <UiTooltip
      htmlFor="tooltip-inverse-trigger"
      inverse
      showDelay={0}
    >Duplicate (⌘D)</UiTooltip>
  </>;
}
/** @jsxImportSource preact */
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="tooltip-inverse-trigger"
    >Shortcut</UiButton>
    <UiTooltip
      htmlFor="tooltip-inverse-trigger"
      inverse
      showDelay={0}
    >Duplicate (⌘D)</UiTooltip>
  </>;
}
<script>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

<UiButton
  id="tooltip-inverse-trigger"
>Shortcut</UiButton>
<UiTooltip
  for="tooltip-inverse-trigger"
  inverse
  showDelay={0}
>Duplicate (⌘D)</UiTooltip>
import { Component } from "@angular/core";
import { UiButton, UiTooltip } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiButton, UiTooltip],
  template: `
    <ng-template
      ui-button
      [hnAttributes]="{ id: 'tooltip-inverse-trigger' }"
    >Shortcut</ng-template>
    <span
      ui-tooltip
      [for]="'tooltip-inverse-trigger'"
      [inverse]="true"
      [showDelay]="0"
    >Duplicate (⌘D)</span>
  `,
})
export default class Example {}

Side placement

A tooltip can sit on any side of its trigger; here it opens on the right and stays centered vertically.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-tooltip/ui-tooltip.html"
>

<ui-button
  id="side-tooltip-trigger"
>Hover or focus</ui-button>
<ui-tooltip
  for="side-tooltip-trigger"
  placement="right"
>This hint opens beside the control.</ui-tooltip>
<script setup>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

<template>
  <UiButton
    id="side-tooltip-trigger"
  >Hover or focus</UiButton>
  <UiTooltip
    for="side-tooltip-trigger"
    placement="right"
  >This hint opens beside the control.</UiTooltip>
</template>
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="side-tooltip-trigger"
    >Hover or focus</UiButton>
    <UiTooltip
      htmlFor="side-tooltip-trigger"
      placement="right"
    >This hint opens beside the control.</UiTooltip>
  </>;
}
/** @jsxImportSource preact */
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="side-tooltip-trigger"
    >Hover or focus</UiButton>
    <UiTooltip
      htmlFor="side-tooltip-trigger"
      placement="right"
    >This hint opens beside the control.</UiTooltip>
  </>;
}
<script>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

<UiButton
  id="side-tooltip-trigger"
>Hover or focus</UiButton>
<UiTooltip
  for="side-tooltip-trigger"
  placement="right"
>This hint opens beside the control.</UiTooltip>
import { Component } from "@angular/core";
import { UiButton, UiTooltip } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiButton, UiTooltip],
  template: `
    <ng-template
      ui-button
      [hnAttributes]="{ id: 'side-tooltip-trigger' }"
    >Hover or focus</ng-template>
    <span
      ui-tooltip
      [for]="'side-tooltip-trigger'"
      [placement]="'right'"
    >This hint opens beside the control.</span>
  `,
})
export default class Example {}

Keyboard shortcut

The shortcut slot shows a key binding after the label, behind a thin divider, in smaller text. It is a display hint and binds no key; write the keys the way the reader's platform does.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-tooltip/ui-tooltip.html"
>

<ui-button
  id="tooltip-shortcut-trigger"
>Search</ui-button>
<ui-tooltip
  for="tooltip-shortcut-trigger"
  open
  placement="bottom"
>
  Search
  <kbd
    slot="shortcut"
  >⌘K</kbd>
</ui-tooltip>
<script setup>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

<template>
  <UiButton
    id="tooltip-shortcut-trigger"
  >Search</UiButton>
  <UiTooltip
    for="tooltip-shortcut-trigger"
    :open="true"
    placement="bottom"
  >Search<template #shortcut>
      <kbd>⌘K</kbd>
    </template></UiTooltip>
</template>
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="tooltip-shortcut-trigger"
    >Search</UiButton>
    <UiTooltip
      htmlFor="tooltip-shortcut-trigger"
      open
      placement="bottom"
      slots={{
        shortcut: <kbd>⌘K</kbd>,
      }}
    >Search</UiTooltip>
  </>;
}
/** @jsxImportSource preact */
import { UiButton, UiTooltip } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="tooltip-shortcut-trigger"
    >Search</UiButton>
    <UiTooltip
      htmlFor="tooltip-shortcut-trigger"
      open
      placement="bottom"
      slots={{
        shortcut: <kbd>⌘K</kbd>,
      }}
    >Search</UiTooltip>
  </>;
}
<script>
  import { UiButton, UiTooltip } from "@threadlabs/looma";
</script>

{#snippet shortcut()}
  <kbd>⌘K</kbd>
{/snippet}

<UiButton
  id="tooltip-shortcut-trigger"
>Search</UiButton>
<UiTooltip
  for="tooltip-shortcut-trigger"
  open
  placement="bottom"
  slots={{ shortcut }}
>Search</UiTooltip>
import { Component } from "@angular/core";
import { UiButton, UiTooltip } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiButton, UiTooltip],
  template: `
    <ng-template
      ui-button
      [hnAttributes]="{ id: 'tooltip-shortcut-trigger' }"
    >Search</ng-template>
    <span
      ui-tooltip
      [for]="'tooltip-shortcut-trigger'"
      [open]="true"
      [placement]="'bottom'"
    >Search<ng-container ngProjectAs="[slot='shortcut']">
        <kbd>⌘K</kbd>
      </ng-container></span>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: span

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
forID of the control described and visually anchored by the tooltip.forstring—
hide-delayWaits this many milliseconds before hiding after pointer exit.hideDelaynumber100—
inverseUse the high-contrast inverse surface (dark on light themes, light on dark themes).inversebooleanfalse—
openWhether the tooltip is open initially or when set externally.openbooleanfalse—
placementPositions the description around its trigger. Start and end on top or bottom follow the writing direction.placementkeywordtoptop, top-start, top-end, bottom, bottom-start, bottom-end, left, left-start, left-end, right, right-start, right-end
show-delayWaits this many milliseconds before showing after pointer entry.showDelaynumber500—
triggerHow the trigger opens it. hover also opens on keyboard focus; click toggles it and suits a help button, where hovering a question mark says nothing; focus opens only on keyboard-visible focus. Action activation dismisses hover/focus hints without removing focus; a fresh hover or keyboard focus can open them again.triggerkeywordhoverhover, click, focus

Framework props

NameTypeDefaultAllowed values
forstring—
hideDelaynumber100—
inversebooleanfalse—
openbooleanfalse—
placement"top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end"toptop, top-start, top-end, bottom, bottom-start, bottom-end, left, left-start, left-end, right, right-start, right-end
showDelaynumber500—
trigger"hover" | "click" | "focus"hoverhover, click, focus

Events

NameDetailDescription
closeobject
  • open: boolean (required)
  • reason: keyword (required)
  • trigger: keyword (required)
Emitted when the component transitions to a closed state.
openobject
  • open: boolean (required)
  • reason: keyword (required)
  • trigger: keyword (required)
Emitted when the component transitions to an open state.

Slots

NamePurpose
defaultDefault child content.
shortcutNamed content for the shortcut region.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

TokenDeclared valuesFallback
--ui-tooltip-arrow-size—0.625rem
--ui-tooltip-border—var(--ui-overlay-border, var(--ui-border))
--ui-tooltip-inverse-surface—var(--ui-text-primary)
--ui-tooltip-inverse-text—var(--ui-surface-default)
--ui-tooltip-max-inline-size—18rem
--ui-tooltip-radius—var(--ui-overlay-radius, var(--ui-radius-sm))
--ui-tooltip-shadow—var(--ui-overlay-shadow, var(--ui-elevation-tooltip, var(--ui-shadow-sm)))
--ui-tooltip-surface—var(--ui-overlay-surface, var(--ui-surface-elevated)) | var(--ui-material-content, var(--ui-overlay-surface, var(--ui-surface-elevated)))
--ui-tooltip-text—var(--ui-text-primary)

Shared tokens

TokenFallback
--ui-border—
--ui-border-width—
--ui-elevation-tooltipvar(--ui-shadow-sm)
--ui-font-family-sans—
--ui-font-normal—
--ui-font-size-2xs—
--ui-font-size-xs—
--ui-glassiness0
--ui-line-height-tight—
--ui-material-contentvar(--ui-overlay-surface, var(--ui-surface-elevated))
--ui-motion-ease—
--ui-motion-fast—
--ui-overlay-bordervar(--ui-border)
--ui-overlay-radiusvar(--ui-radius-sm)
--ui-overlay-shadowvar(--ui-elevation-tooltip, var(--ui-shadow-sm))
--ui-overlay-surfacevar(--ui-surface-elevated)
--ui-radius-sm—
--ui-shadow-sm—
--ui-space-1—
--ui-space-2—
--ui-space-3—
--ui-surface-default—
--ui-surface-elevated—
--ui-text-primary—