npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@eburlak/tooltip

v0.3.3

Published

Accessible, zero-dependency tooltip for React and vanilla JS: one shared element that travels between anchors, data-tooltip attributes, themes, TypeScript, ~2.3 kB gzipped

Readme

@eburlak/tooltip

npm gzip size types license

Accessible, zero-dependency tooltip for React and vanilla JS. One shared element travels between anchors - position and size morph instead of popping in and out.

Live demo

  • Markup only - data-tooltip="Save" on any element and new Tooltip(); elements added later work too.
  • React and plain HTML - the same attributes in JSX, or spreadable onMouseEnter/onFocus listeners.
  • Accessible - role="tooltip", aria-describedby, keyboard focus, Escape, prefers-reduced-motion.
  • Smart placement - flips to the other side when there is no room and stays inside the viewport.
  • Themable at runtime - colors, font, radius, width and speed; configure() restyles an open tooltip with an animation.
  • Tiny - about 2.3 kB gzipped, no dependencies, TypeScript types included.

An instance mounts one <div role="tooltip"> and one <style> on the first show; every anchor it serves reuses the same element. Create one instance per app and share it: every instance listens to its root, so two instances on one root would both answer the same anchor.

npm install @eburlak/tooltip

Markup

The constructor delegates listeners to root (the whole document by default) and serves every [data-tooltip] under it, including elements added later. On the server, where there is no document, it skips listening, so an instance can be created at module level. While a tooltip is open its anchor is observed: a new data-tooltip or data-tooltip-placement is applied right away: the text is replaced instantly while the tooltip resizes and moves smoothly, removing the text hides it.

<button data-tooltip="Save changes">Save</button>
<button data-tooltip="Delete forever" data-tooltip-placement="bottom">Delete</button>

<script type="module">
  import { Tooltip } from 'https://cdn.jsdelivr.net/npm/@eburlak/tooltip/+esm';

  const tooltip = new Tooltip({ theme: { background: '#3b5bdb' } });
</script>

Without modules (works from file:// too) - dist/index.global.js sets window.Tooltip. It is also the default file on unpkg and jsDelivr, and @eburlak/tooltip/global for bundlers:

<script src="https://cdn.jsdelivr.net/npm/@eburlak/tooltip"></script>
<script>
  new Tooltip();
</script>

React

The same attributes work in JSX once the instance is created, and a re-render that changes the text updates the open tooltip (data-tooltip={isCopied ? 'Copied!' : 'Copy'}):

// tooltip.ts
import { Tooltip } from '@eburlak/tooltip';

export const tooltip = new Tooltip({ theme: { maxWidth: 240 } });
<button data-tooltip="Save changes">Save</button>
<a href="#" data-tooltip="Open" data-tooltip-placement="bottom">Open</a>

Or spread listeners on an element without data-tooltip; their text is fixed when they are created, call tooltip.show(anchor, text) to change an open tooltip:

<button {...tooltip.getListeners('Save changes')}>Save</button>

API

| Method | Purpose | | --- | --- | | new Tooltip({ placement, offset, theme, root }) | defaults: 'top', 8, theme below, document; listens to [data-tooltip] / [data-tooltip-placement] under root | | getListeners(text, placement?) | { onMouseEnter, onMouseLeave, onFocus, onBlur } to spread on a React element | | attach(element, text, placement?) | the same listeners via addEventListener, returns detach() | | show(anchor, text, placement?) / hide(anchor?) | manual control; with an anchor, hide does nothing once the tooltip has moved to another one | | configure({ placement, offset, theme }) | merges into the current options; a theme regenerates the mounted styles and an open tooltip animates into it | | destroy() | stops listening, removes markup and styles; a manual show mounts them again | | id | random element id, tooltip- plus 8 characters, unique per instance |

Theme

Any subset, merged with the defaults; styles are generated from the values, no CSS variables.

| Key | Default | | --- | --- | | background | '#1f2329' | | color | '#fff' | | font | '12px/1.4 system-ui, sans-serif' | | radius | 6 (px) | | maxWidth | 280 (px) | | zIndex | 10000 | | duration | 200 (ms, every animation) |

Fades in sliding down and fades out sliding up. Moving to another anchor while still visible slides and resizes it, the text is replaced instantly; an 80ms hide delay keeps it opaque while the pointer crosses the gap. Hides on mouse leave, blur, Escape and any scroll, follows its anchor when the window is resized. Flips to the other side when the preferred one does not fit and clamps to the viewport horizontally.

Animations are off under prefers-reduced-motion.

Scripts

npm run build - a clean dist/ via tsc plus dist/index.global.js, npm test - vitest + jsdom, example/index.html - the demo, open it straight from disk after the build; .github/workflows/demo.yml publishes it to GitHub Pages on every push to main.

npm publish builds the package on prepack and runs the tests on prepublishOnly.

License

MIT