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

react-tooltip-contemporary

v0.1.2

Published

contemporary react tooltip — CSS clip-path shape, native Popover API, CSS anchor positioning

Readme

Contemporary React Tooltip

Contemporary React Tooltip npm version npm downloads

Demo

A small 8kB gzipped and no dependency React tooltip built on modern web features:

  • CSS anchor positioning: the bubble pins itself to its trigger with anchor-name / position-anchor / anchor(); no JS measuring on scroll. Old browsers degrade gracefully to title tooltip, no polyfill, no extra dependency.
  • Popover API: the bubble lives in browser's top layer, it escapes overflow: hidden and z-index stacking with no portal.
  • Pure CSS shape: the rounded bubble and its arrow are one clip-path: polygon(...), no borders, no pseudo-elements or SVG.
  • Zero-config styling: each component injects its own stylesheet slice at runtime; no CSS import and no bundler CSS loader required.

| Mode | Markup | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Wrapping (default) | <Tooltip content="Saved"> <button>Save</button></Tooltip> | | External by ref | <button ref={ref}>Save</button><Tooltip anchorRef={ref} content="Saved" /> | | External by name | <button style={{ anchorName: '--s' }}>Save</button><Tooltip anchorName="--s" content="Saved" open={open} onOpenChange={setOpen} /> |

Components

Each component injects its own stylesheet slice at runtime, so all three work standalone with no CSS import.

| Export | Role | | --------------- | -------------------------------------------- | | Tooltip | Behaviour, triggers, positioning. | | TooltipShape | The bubble, e.g the clip-path shape + arrow. | | TooltipAnchor | The anchor part of CSS anchor positioning. |

Tooltip props

| Prop | Type | Default | Notes | | ---------------- | ---------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------ | | children | ReactNode | – | The trigger element (wrapping mode). | | content | ReactNode | – | The bubble content. | | placement | 'top' \| 'bottom' \| 'left' \| 'right' | 'top' | Preferred side of the anchor. | | arrowPlacement | 'start' \| 'center' \| 'end' | 'center' | Which way the bubble extends; arrow stays on the anchor centre (see below). | | trigger | ('hover' \| 'focus' \| 'click')[] | ['hover', 'focus'] | Interactions that reveal the tooltip. | | delayShow | number | 200 | ms before showing (hover/focus only; click is instant). | | delayHide | number | 100 | ms before hiding (hover/focus only; click is instant). | | offset | string | '0.25em' | Gap between anchor and bubble (any CSS length). | | autoFlip | boolean | true | Flip to the opposite side when it would overflow. | | defaultOpen | boolean | false | Initial open state (uncontrolled). | | open | boolean | – | Controlled open state; pair with onOpenChange. | | onOpenChange | (open: boolean) => void | – | Fires when the open state should change. | | bubbleStyle | TooltipBubbleStyle | – | Bubble appearance (see below). | | className | string | – | Applied to the popover element. | | style | CSSProperties | – | Applied to the popover element. | | anchorRef | RefObject<HTMLElement> | – | Attach to an existing element instead of wrapping children. See External anchor below. | | anchorName | string | – | Use this CSS anchor name verbatim. See External anchor below. |

arrowPlacement

The arrow always points at the anchor's centre, arrowPlacement only chooses which way the bubble body extends from it. 'center' (default) centres the bubble on the anchor; 'start' keeps the arrow near the bubble's leading edge so the body grows toward the trailing side; 'end' mirrors that. Handy when the anchor sits near a viewport edge and you want the bubble to grow the other way. The axis follows placement: left→right for top/bottom, top→bottom for left/right.

<Tooltip content="Aligned to the start" placement="top" arrowPlacement="start">
  <button>Hover me</button>
</Tooltip>

bubbleStyle

Per-instance look of the bubble. Pass any subset; omitted fields fall back to the library defaults. Most fields are applied as CSS custom properties on the bubble. The one exception is cornerSegments, which selects a .corners-N class.

| Field | Type | Default | Notes | | -------------------- | ------------- | ------------ | --------------------------------------------------------- | | background | string | '#000' | Bubble background (any CSS background). | | color | string | '#fff' | Text color. | | fontSize | string | '0.875rem' | Bubble font size. | | radius | string | '0.5rem' | Corner radius (any CSS length). | | arrowSize | string | '0.5rem' | Arrow size (half-diagonal). | | paddingX | string | '0.7rem' | Horizontal padding. | | paddingY | string | '0.4rem' | Vertical padding. | | maxWidth | string | '16rem' | Maximum bubble width. | | transitionDuration | string | '0.2s' | Fade in/out (and flip) duration. | | cornerSegments | 3 \| 5 \| 7 | 5 | Straight segments per rounded corner, higher is smoother. |

<Tooltip
  content="Custom bubble"
  bubbleStyle={{
    background: '#2563eb',
    color: '#fff',
    radius: '0.8rem',
    arrowSize: '0.6rem',
    paddingX: '1rem',
    paddingY: '0.5rem',
    maxWidth: '20rem',
    transitionDuration: '0.25s',
    cornerSegments: 7,
  }}
>
  <button>Hover me</button>
</Tooltip>

Controlled usage

const [open, setOpen] = useState(false);

<Tooltip open={open} onOpenChange={setOpen} content="Controlled">
  <button>Anchor</button>
</Tooltip>;

External anchor (skip the wrapper)

When you'd rather attach the tooltip to an element you already render. Without Tooltip wrapping it in an extra <div>. Pass anchorRef and omit children. Tooltip writes anchor-name onto the referenced element, wires the configured triggers to it, and mirrors aria-describedby on it for accessibility:

const btnRef = useRef<HTMLButtonElement>(null);

<>
  <button ref={btnRef}>Save</button>
  <Tooltip anchorRef={btnRef} content="Saved" />
</>;

If you'd rather own the CSS anchor name yourself, use anchorName. In this mode Tooltip has no handle to your element, so it cannot wire triggers; pair with controlled open / onOpenChange:

const [open, setOpen] = useState(false);

<>
  <button
    style={{ anchorName: '--save-btn' } as CSSProperties}
    onMouseEnter={() => setOpen(true)}
    onMouseLeave={() => setOpen(false)}
  >
    Save
  </button>
  <Tooltip
    anchorName="--save-btn"
    open={open}
    onOpenChange={setOpen}
    content="Saved"
  />
</>;

Browser support

Where CSS anchor positioning is missing, there is no polyfill and no extra dependency. The styled bubble is skipped and string content is surfaced through the element's native title tooltip instead.

Development

npm install
npm run dev        # Storybook + css-to-js watcher
npm run build      # type-check, build to lib/, generate + copy CSS
npm test

Each component owns a *.css file; css-to-js regenerates the matching *.css.generated.js, which the component injects at runtime.

License

MIT © Sergey Yakunin