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

plumbkit

v0.2.0

Published

A dev-tool overlay that draws alignment guides over any running web app.

Readme

plumbkit

A dev-tool overlay that draws alignment guides over any running web app. Click two elements and plumbkit projects their edges across the viewport so you can see which lines up, which is off by a few pixels, and exactly how much — then hands the fix straight to your coding agent.

It's dev-only by design: everything is gated behind process.env.NODE_ENV (replaced statically by Vite, webpack, Turbopack, and esbuild), so a production build tree-shakes the entire tool away to nothing.

Install

npm i -D plumbkit          # or: pnpm add -D plumbkit / yarn add -D plumbkit

Zero runtime dependencies in the core. The React adapter has a single optional peer dependency on react (>= 16.8).

Quick start

Pick whichever fits your app — all three mount the same overlay.

React

import { Plumb } from 'plumbkit/react'

export function App() {
  return (
    <>
      <YourApp />
      <Plumb />
    </>
  )
}

<Plumb> mounts an instance on mount and tears it down on unmount; it renders nothing. It's enabled in dev by default (process.env.NODE_ENV !== 'production') and accepts every createPlumb option as a prop.

Next.js (App Router): wrap it in a client component and render it in the root layout, gated on dev:

// components/plumb-devtools.tsx
'use client'
import { Plumb } from 'plumbkit/react'
export function PlumbDevtools() {
  return <Plumb />
}

// app/layout.tsx
{process.env.NODE_ENV === 'development' && <PlumbDevtools />}

Vite plugin (zero app code)

// vite.config.ts
import { defineConfig } from 'vite'
import { plumbkit } from 'plumbkit/vite'

export default defineConfig({
  plugins: [plumbkit()],
})

The overlay is injected by the dev server itself — nothing to import in app code. The plugin only exists under vite dev (apply: 'serve'), so production builds can't contain a trace of it.

Auto (zero config, any bundler)

import 'plumbkit/auto'

One import at your entry point and the overlay mounts itself in dev. The instance is exposed as window.__PLUMB__ for console poking.

Script tag (no install — try it on any page)

<script type="module" src="https://unpkg.com/plumbkit/dist/auto.js"></script>

Core (imperative)

import { createPlumb } from 'plumbkit'

const plumb = createPlumb()

plumb.activate()                 // arm the tool (or click the on-screen button)
plumb.select('#card')            // add an element to the selection
plumb.on('change', ({ selection }) => console.log(selection))
plumb.destroy()                  // remove everything

// In production createPlumb returns an inert handle before touching the DOM,
// and the whole implementation is dropped from the bundle.

Using it

The bar spawns in the bottom-left (lifting itself above the Next.js dev indicator or any other widget already in that corner) with three always-visible toggles: the crosshair (alignment guides — A or Alt+G), padding (box-model overlay — P), and measure (distances between selections — M). Each toggles independently and they layer: any tool being on enables hover + click-to-select, so you can inspect spacing without guides, then add guides on top. Hovering a button shows a tooltip, and each active tool stacks a short description card top-right. The first few times you turn a tool on, three small key hints appear top-left (↑↓ walk the tree, Enter toggle, Esc clear) — each retires for good once you've actually used its key, so they teach the controls and then get out of the way. Drag the bar to park the dock anywhere — the spot persists across reloads.

Once armed:

  • Click any element to select it. Its edges — left · center · right and top · middle · bottom — draw as hairlines across the whole viewport.
  • Click more elements to compare. Click a selected element again to drop it.
  • Shift-click to click through the tool: the click reaches your app as a normal click, so you can open a dropdown or press a button without disarming, then release Shift and inspect it. While Shift is held the hover highlight hides to show you're in pass-through mode.
  • Keyboard: A / P / M toggle the guides / padding / measure tools; arrow keys walk the DOM tree ( parent, child, ←ↅ siblings), Enter / Space toggles the highlighted element, Esc clears.

Padding inspection

Flip the padding sub-toggle to overlay the box model of whatever you hover or select — the way browser devtools shade padding, but live over your app:

  • Padding fills green, with a per-side pixel label on each non-zero side.
  • Flex / grid gaps fill in a second colour with a single gap label.
  • Margins fill orange (no UI toggle — opt in with the showMargins option).

It's border-aware (bands sit inside the border), ignores drop shadows, and runs alongside the alignment guides — leave both on at once.

Measuring between elements

Flip the measure toggle (two-boxes icon), then click two elements: the pixel distance between their nearest edges draws as a magenta ticked line with a label — horizontally, vertically, or both when they sit diagonally apart. Selecting more elements chains the measurements in click order (A↔B, B↔C, …); overlapping elements have no gap, so nothing draws for that pair.

Spacing consistency

Select a container and plumbkit also checks whether its direct children are spaced consistently, adding up to two rows to the legend:

  • Spacing — whether the children share one padding pattern: a green swatch means consistent, an amber dashed one means drift. Any child that misses the dominant padding by a few px gets an amber pt +2 / pl −3 label on the offending side.
  • Rhythm — for children spaced with margins (not a flex/grid gap), the number of distinct inter-child gaps. 1 is an even rhythm; more is uneven.

Both reuse the same tolerance clustering as the alignment engine, and absolutely-positioned children are excluded.

When two or more elements are selected, the guide lines are colour- and pattern-coded:

| Line | Meaning | | --- | --- | | Green, solid | edges that align exactly | | Amber, dashed | edges that nearly align, with a signed ±px delta label | | Cobalt, faint | lone edges with no relationship (dimmed so the signal stands out) |

Two panels appear top-right: a legend + tally (how many aligned / near / lone), and "Slightly off", which lists every near-miss with a Copy fix for agent button — it puts a ready-to-paste prompt on your clipboard describing exactly which edges are off and by how much. A separate Copy names button copies the selected components' names and location (React component + DOM path + nearby text) so you can drop them into an agent and say "align these."

API

createPlumb(options?) returns a PlumbHandle:

| Method | Description | | --- | --- | | toggle() | Arm or disarm the tool. | | activate() / deactivate() | Arm / disarm explicitly. | | select(target) | Add an Element (or CSS selector) to the selection; arms if needed. | | clear() | Clear the current selection. | | on(event, fn) | Subscribe; returns an unsubscribe function. | | destroy() | Tear down the overlay and all listeners. |

Events: toggle{ active }, select{ target }, change{ selection }.

Options

createPlumb(options) / <Plumb {...options} />:

| Option | Type | Default | Description | | --- | --- | --- | --- | | enabled | boolean | dev only | Master switch; false → inert, no DOM work. | | axes | AxisKey[] | all six | Which edges/centers to project: left,centerX,right,top,centerY,bottom. | | threshold | number | 2 | Gap (px) under which two same-kind edges count as aligned. | | nearTolerance | number | 16 | Max gap (px) between same-kind edges of elements that share a row/column to flag a near-miss. | | spacing | boolean | false | Start with the spacing box-model overlay on (toggle it live from the sub-button). | | measure | boolean | false | Start with the measure overlay on — distances between selected elements (toggle it live from the sub-button). | | showMargins | boolean | false | Show margin bands in the spacing overlay (only relevant with spacing; API-only, no UI toggle). | | theme | PlumbTheme | — | Overrides for the --plumb-* colour/size tokens. | | color | string | cobalt | Shorthand for theme.accent. | | hotkey | string | 'alt+g' | Shortcut that arms/disarms the tool. | | root | HTMLElement | document.body | Where the overlay host mounts. |

Theming

Every colour and size is a --plumb-* custom property scoped to the overlay's shadow host (defined in OKLCH). Override any subset via theme:

createPlumb({
  theme: {
    accent: 'oklch(0.7 0.16 320)', // selected / lone edges, armed button
    match: 'oklch(0.8 0.16 145)',  // aligned edges
    near: 'oklch(0.8 0.15 70)',    // near-miss edges
    bg: 'oklch(0.2 0.02 300)',
    radius: '6px',
  },
})

Keys include accent, match, near, lone, warn, bg, bgAlt, fg, muted, border, ink, shadow, fab, gap, radius, lineWidth, the spacing fills paddingFill / gapFill / marginFill, the measure line colour, and the *Opacity / labelSize / fontSans guide tokens.

How alignment detection works

For the current selection, plumbkit:

  1. Measures each selected element's rect (skipping its own UI and anything display:none or zero-size).
  2. Derives up to six axis coordinates per rect: left / centerX / right (→ vertical guides) and top / centerY / bottom (→ horizontal guides).
  3. Clusters per axis in 1-D — sorted, single pass, starting a new cluster when the gap to the previous value exceeds threshold; a cluster's coordinate is the median of its members. Clustering within each axis means a right edge sitting next to a left edge is read as spacing, not alignment — it's never mis-flagged.
  4. Classifies each cluster:
    • aligned — two or more same-kind edges share the line (green).
    • near — a lone edge that misses another same-axis edge whose element shares its row/column (amber, dashed, with a signed ±px delta). Two side-by-side panels compare their tops/bottoms (same row) but not their lefts (different columns), so intentional layout gaps aren't flagged.
    • lone — nothing nearby (cobalt, faint).

Guides span the full viewport, so elements far apart on the page can still be compared. Everything is drawn in raw getBoundingClientRect viewport coordinates (the host is position:fixed) and re-tracked on scroll, resize, and a throttled rAF heartbeat — no scroll math.

Accessibility & fit

  • Keyboard-operable end to end (DOM-tree element picker, focusable controls with visible focus rings).
  • Not colour-only — near-miss lines are dashed as well as amber, and status changes are announced via an aria-live region.
  • Respects prefers-reduced-motion.
  • Stays out of the way — the chrome is corner-docked and flips sides to avoid fixed app widgets; it never dismisses your app's own modals/popovers.
  • Isolated in a shadow DOM at max z-index, with its font bundled as a data: URI so it renders even under a strict Content-Security-Policy.

License

MIT