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

directional-persistence

v0.2.0

Published

React hooks for directional persistence animations — tracking pills, hover cards, and popovers that follow user movement

Readme

directional-persistence

React hooks for directional persistence animations — tracking pills, hover cards, and popovers that follow the user's movement through a sequence.

Zero dependencies. React 18+. SSR-safe. GPU-composited.

Install

npm install directional-persistence

Two Strategies

| | useTrackingPill | DirectionalGroup | |---|---|---| | Pattern | Single persistent element moves between positions | Content mounts/unmounts with directional context | | Use cases | Tab pills, hover highlights, nav indicators | Popovers, hover cards, detail panels | | Animation | CSS transitions on transform | CSS animations via data attributes | | Re-renders | None (direct DOM manipulation) | Only on active item change |

useTrackingPill

A single highlight/pill element that tracks the user's position across a set of items.

Modes

  • glide (default) — CSS transitions on position. Smooth "Vercel tabs" feel.
  • snap — Teleport with directional offset, then slide into place. Snappier.

Usage

import { useTrackingPill } from 'directional-persistence';

function Tabs({ items }) {
  const pill = useTrackingPill({ mode: 'glide' });

  return (
    <nav ref={pill.containerRef} onMouseLeave={pill.hide} style={{ position: 'relative' }}>
      <div
        ref={pill.pillRef}
        style={{
          position: 'absolute',
          background: 'rgba(0, 0, 0, 0.05)',
          borderRadius: 8,
          pointerEvents: 'none',
        }}
      />
      {items.map(item => (
        <button
          key={item.id}
          onMouseEnter={e => pill.track(e.currentTarget)}
        >
          {item.label}
        </button>
      ))}
    </nav>
  );
}

Options

useTrackingPill({
  mode: 'glide',     // 'glide' | 'snap' — default: 'glide'
  duration: 150,     // transition duration in ms — default: 150
  easing: 'ease-out', // CSS easing — default: 'ease-out'
  snapOffset: 8,     // snap mode offset in px — default: 8
})

Returns

| Property | Type | Description | |---|---|---| | containerRef | RefObject<HTMLElement> | Attach to the wrapping container | | pillRef | RefObject<HTMLElement> | Attach to the pill/highlight element | | track(target) | (el: HTMLElement) => void | Move the pill to a target element | | hide() | () => void | Hide the pill (e.g. on mouse leave) | | isVisible | boolean | Whether the pill is currently showing |

DirectionalGroup

Context provider + hooks for content that mounts/unmounts with directional awareness. Perfect for popovers and hover cards that need to animate based on which direction the user came from.

Usage

import { DirectionalGroup, useGroupItem, useGroupContent } from 'directional-persistence';

function NavList({ items }) {
  return (
    <DirectionalGroup openDelay={100} closeDelay={100}>
      {items.map((item, i) => (
        <NavItem key={item.id} index={i} item={item} />
      ))}
    </DirectionalGroup>
  );
}

function NavItem({ index, item }) {
  const { triggerProps, isActive } = useGroupItem(index);

  return (
    <div style={{ position: 'relative' }}>
      <button {...triggerProps}>{item.label}</button>
      {isActive && <Popover index={index} content={item.content} />}
    </div>
  );
}

function Popover({ index, content }) {
  const { direction, isTransition, contentProps } = useGroupContent(index);

  return (
    <div {...contentProps} className="popover">
      {content}
    </div>
  );
}

Styling with data attributes

The contentProps include data-direction and data-transition attributes:

/* Vanilla CSS */
[data-direction='forward'] {
  animation: slide-forward 150ms ease-out;
}
[data-direction='backward'] {
  animation: slide-backward 150ms ease-out;
}
[data-direction='none'] {
  animation: fade-in 150ms ease-out;
}
{/* Tailwind */}
<div
  {...contentProps}
  className="data-[direction=forward]:animate-slide-right data-[direction=backward]:animate-slide-left"
/>

Default CSS

Import the optional stylesheet for sensible default animations:

import 'directional-persistence/styles';

Props & API

<DirectionalGroup>

| Prop | Type | Default | Description | |---|---|---|---| | openDelay | number | 100 | Delay before opening on first hover (ms) | | closeDelay | number | 100 | Grace period before closing on leave (ms) |

useGroupItem(index) returns:

| Property | Type | Description | |---|---|---| | triggerProps | object | Spread onto the trigger element | | isActive | boolean | Whether this item is currently active |

useGroupContent(index) returns:

| Property | Type | Description | |---|---|---| | direction | 'forward' \| 'backward' \| null | Direction relative to previous item | | isTransition | boolean | true when switching between items (vs. first open) | | contentProps | object | Spread onto content for gap bridging + data attributes |

How it works

Tracking Pill — Direct DOM manipulation via refs. No React re-renders on hover. Uses translate3d for GPU-composited transforms. In snap mode: disables transition → teleports with offset → forces reflow → re-enables transition → animates to final position.

Directional Group — Two independent timers manage open/close behavior. First entry has a configurable delay; subsequent switches are instant. A close grace period allows the cursor to move between trigger and content without flickering. Direction is computed by comparing the current index to the previous one.

License

MIT