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

@patternmode/verge

v0.1.6

Published

A reserved slot whose controls rest hidden and reveal on hover, focus, or touch — with no layout shift.

Downloads

46

Readme

@patternmode/verge

A reserved slot whose controls rest hidden and reveal on hover, on focus, and always on touch — holding their space throughout, so nothing moves.

The pattern every dense list uses: a row shows only its data at rest, and its edit / delete / ··· controls appear when you address it. Registries ship the button, the dropdown and the tooltip — the things being revealed. None ships the contract governing when they appear, which is where hand-rolled versions drift apart.

npm install @patternmode/verge
import { Verge } from "@patternmode/verge";
import "@patternmode/verge/styles.css";

<Verge.Root as="li">
  <span>{item.name}</span>
  <Verge.Slot slots={2}>
    <IconButton label="Edit" />
    <OverflowMenu />
  </Verge.Slot>
</Verge.Root>;

Requires Tailwind CSS v4.

What it already handles

So you don't rebuild something that is one import away:

| | | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Keyboard | Tabbing into the row reveals the slot, via :focus-within on the root. Children stay in the tab order at rest, which is what lets focus arrive at all. | | Touch | No hover exists, so nothing hides. Handled with @media (hover: none), not a tap handler. Controls also get a wider gap on coarse pointers, because permanently-visible controls 2px apart are not separable by a thumb. | | Open menus | A slot holding an open menu, popover or disclosure stays revealed — [data-popup-open], [data-state="open"] and [aria-expanded="true"] are all recognised, so the ··· does not vanish when the pointer travels to the menu it just opened. | | Layout stability | Only opacity and pointer-events change. The slot occupies its space at rest and revealed alike. | | Nesting | Roots may nest. A nested root shadows its ancestor's state for its own subtree, so pointing at an outer row does not reveal an inner row's controls. | | Column alignment | slots={n} reserves n controls' worth of width so rows carrying different numbers of controls still align on one axis. | | Reduced motion | The transition is dropped under prefers-reduced-motion: reduce. | | Semantic parents | as renders the real element, so a row inside a <ul> can be an <li> and a cell can be a <td>. |

What you must handle: touch hit areas

On a touch surface verge makes the controls permanently visible, because there is no hover to reveal them. That is the correct behaviour, and it hands you an obligation: those controls are now always under a thumb, so each needs a 48×48px minimum hit area.

Verge reserves the slot; it does not own your button, so it cannot do this for you. Keep the control visually small and expand its hit area on coarse pointers only:

<button className="relative size-7">
  <span
    className="pointer-fine:hidden absolute top-1/2 left-1/2 size-[max(100%,3rem)] -translate-1/2"
    aria-hidden="true"
  />
  <Icon className="size-4" />
</button>

3rem is 48px; pointer-fine:hidden drops the padded area on mouse and trackpad where it is not needed. Define the variant once if your project does not have it:

@custom-variant pointer-fine (@media (pointer: fine));

Parts

Verge.Root

The container whose hover and focus state the slots read. Wrap the whole row, not just the controls — the reveal responds to the user addressing the row, which is the thing they are pointing at. Contributes no appearance of its own.

Verge.Slot

The reserved area holding the controls.

| Prop | Type | Notes | | --------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | slots | number | Controls' worth of width to hold open. Only needed when controls must align into a column across rows carrying different numbers of them. | | visible | boolean | Keeps the slot revealed regardless of pointer or focus — for state the CSS cannot see, such as an action mid-flight. Not an escape hatch for "this row matters more"; a control drawn on every row at rest is furniture. | | as | ElementType | Element to render. Defaults to div. |

Theming

Every knob is a custom property, so retune without forking. Set them anywhere above the slot — :root, or the list that owns the rows — and they inherit; verge declares none of them on the slot itself, so nothing shadows your value.

.finish-list {
  /* One control's 28px plus the 2px gap between two of them, so `slots`
     reserves exactly what n controls occupy. */
  --patternmode-verge-slot-size: 30px;
}

| Property | Default | | | ------------------------------- | ---------------------------- | -------------------------------------------- | | --patternmode-verge-slot-size | 1.75rem | Width one control reserves, used by slots. | | --patternmode-verge-duration | 120ms | Reveal transition duration. | | --patternmode-verge-easing | cubic-bezier(0.2, 0, 0, 1) | Reveal transition easing. |

Why opacity rather than display: none

The children stay in the tab order while the slot is at rest, which is what lets focus arrive and trigger the reveal. Hiding with display: none or visibility: hidden removes them from the tab order, so focus can never reach them, so they can never reveal themselves — the keyboard path closes on itself. pointer-events: none at rest is what stops the mouse clicking a control it cannot see, and it is restored in the same rule that restores opacity, so a visible control is always clickable and an invisible one never is.

Why a component rather than two utility classes

Surveying two unrelated codebases turned up eleven hand-rolled versions of this pattern, and they disagreed. The recurring defect is the keyboard case: several spelled the second trigger focus-visible instead of focus-within, which reveals the overflow menu two elements away but not the checkbox beside it. None handled touch, because the omission is invisible on the machine the code was written on.

Reveal state rides --patternmode-verge-* custom properties rather than utility class names, so the resting state cannot be beaten by an unrelated rule in the host app that happens to declare opacity: 0. Immunity by construction rather than by out-ranking.

Verifying it in a browser

Two failures are independent and both are invisible in a screenshot of the resting state, so check them separately:

  1. Tab into a row — the controls must appear, and the one you have tabbed to must be clickable, not merely visible.
  2. On a touch surface (or with hover emulation off), the controls must be visible at rest rather than unreachable.