@surstromming/tooltip

A short label on hover or focus.
829
Install
npm install @surstromming/tooltip
Latest Version:0.1.3
Vue:3
License:MIT
Last Updated:Oct 17, 2026
Links: GitHub  ·  npm
Maintainer: evgeniy_mnatsakanov

@surstromming/tooltip

A short label that appears on hover or focus — the name of an icon button, a hint on a truncated value. Text only; anything richer is a Popover.

Dependency graph

graph LR
  tooltip["@surstromming/tooltip"]
  design["@surstromming/design"]
  util["@surstromming/util"]
  tooltip --> design
  tooltip --> util

Usage

<template>
  <Tooltip content="Add to library" side="bottom">
    <Button size="icon" aria-label="Add"><Icon :icon="Plus" /></Button>
  </Tooltip>
</template>

<script setup lang="ts">
import { Tooltip } from '@surstromming/tooltip'
import { Button } from '@surstromming/button'
import { Icon } from '@surstromming/icon'
import { Plus } from 'lucide'
</script>

Props

Prop Type Default Notes
content string — (required) The label text
side top | right | bottom | left top Preferred edge — flips to the opposite one when there's no room
delay number 300 ms before it shows
v-model:open boolean false Optional — usually left to hover/focus

Slots

Slot Description
default The trigger. Keep it focusable so keyboard users get the tip too.

Behavior

  • Shows after delay on mouseenter, and on focusin only while the trigger matches :focus-visible — so the tip appears exactly when the focus ring does. Hides immediately on leave, blur or Escape. A pointer passing through never flashes it; the pending timer is cancelled on the way out.
  • Focus that the browser doesn't draw doesn't show it either. A dialog hands focus back to the control that opened it, so a tooltipped opener used with the mouse got a tip on the way back that nothing could dismiss — the pointer is wherever the dialog's own ✕ was, so no mouseleave is coming for the trigger. A keyboard user still gets the tip on the hand-back, along with the ring.
  • The tip is pointer-events: none, so it can't eat the hover it describes.
  • The tip teleports to <body> and is positioned fixed from the trigger's measured rect (useAnchoredTip), re-measured on scroll and resize. Left inside the trigger's own box it was clipped by any overflow ancestor — and every page here scrolls through a ScrollArea, so a trigger near the top of one had its whole tip cut away.
  • It flips rather than shifting: side is a preference, and when the tip won't fit there it moves to the opposite edge (only if that one fits — otherwise flipping would just move the problem). The cross axis is then clamped 8px inside the viewport. This is the opposite of Popover, which shifts and stays on the side it was asked for: a popover is a surface you work in, a tip is a label.

Tokens

primary / primary-foreground (the inverted chip shadcn uses), radius(md), 0.75rem type, z-index(tooltip) — the ladder's top rung, so it clears every overlay including a dialog's own controls.

Notes

  • The trigger must be focusable (a Button, a link) for the tip to be keyboard-reachable — a tooltip on a bare <span> is mouse-only.

Related Packages

@surstromming/popover

Panel anchored under a trigger; closes on outside click and Escape.

1,481 0
@surstromming/dialog

Modal dialog, with an alert-dialog mode and sheets on the screen's edges.

1,382 0
@surstromming/design

Everything related to design system, including colors, typography, and spacing.

1,363 0
@surstromming/scroll-area

A scroll container with an Edge-style overlay scrollbar.

1,157 0

Version History

Version Released Vue License
0.1.3 ^3.4.0 MIT
0.1.2 ^3.4.0 MIT
0.1.1 ^3.4.0 MIT
0.1.0 ^3.4.0 MIT