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-uidevkit

v0.1.0

Published

In-app UI feedback overlay for React: pick an element or drag a box, describe the issue, and submit a rich context bundle (component chain, DOM, styles, console, screenshot). Dev mode queues captures for the /uidevkit Claude Code command; QA mode submits

Readme

react-uidevkit

Point at an element (or drag a box) in your running React app, describe the issue, and submit a full context bundle — component chain, DOM, styles, console, and a screenshot.

react-uidevkit is an in-app UI-feedback overlay for any React app (framework-agnostic — no Next.js dependency). It runs in two loops from the same component:

  • Dev loop (variant="dev", default) — the local iteration loop. Captures POST to a bundled dev-queue handler that writes each one to .claude/react-uidevkit/queue/, and the /uidevkit Claude Code command fixes every queued capture in order, citing the file:line it changed. Mount it behind a dev guard so it never ships to production.
  • QA loop (variant="qa") — the production feedback loop. The note panel gains severity and category pickers, and captures POST to your own authenticated API instead of the local queue. Mount it only for the users your app has authorized (e.g. a QA role) so real testers can file rich UI issues straight from production.

Both loops use the same two capture modes, driven by Pointer Events (mouse, touch, and pen all work):

| Mode | Trigger | What it does | | ----------- | ----------------------------- | ---------------------------------------------- | | Element | FAB menu or ⌘/Ctrl+Shift+E | Highlight one element, click or tap to pick it | | Area | FAB menu or ⌘/Ctrl+Shift+S | Drag (mouse or finger) a box over a region |

The launcher is a single round FAB on desktop and mobile. Its menu also offers full-screen capture and forced Auto / Mobile / Tablet / Desktop viewport presets. Forced presets emulate widths of 390 / 820 / 1280 CSS pixels by injecting a last-wins viewport meta tag, and persist for the current browser tab via sessionStorage until switched back to Auto.

After you pick, type your note and submit (⌘/Ctrl+⏎). Esc cancels pick mode or closes the note box.

Optional: true-pixel screenshots via the companion extension

For local development, the companion Chrome MV3 extension can provide true-pixel viewport screenshots through chrome.tabs.captureVisibleTab. That path captures Chrome compositor pixels, so sandboxed or cross-origin iframes, WebGL, canvas, and video appear where DOM rasterization may be blank. Its page-facing bridge only exists on localhost/127.0.0.1 (Chrome forces an <all_urls> host grant for this API — see the extension README for the actual exposure), and the overlay falls back to html-to-image when the extension is absent. See integrations/chrome-extension/README.md for install steps and the request/result event protocol. Like area capture, it captures the visible viewport, not the full page.

Install

npm install react-uidevkit
# the screenshot is an optional peer dependency:
npm install html-to-image

Scaffolding: npx react-uidevkit init

Run the initializer from a Next.js App Router or React Router v7 framework-mode app:

npx react-uidevkit init
# or override detection:
npx react-uidevkit init --framework next
npx react-uidevkit init --framework react-router

It creates the framework's dev-queue route, installs .claude/commands/uidevkit.md, ignores the local capture queue, installs the package plus optional html-to-image, and prints the route registration and overlay mount snippets. Existing generated files are left alone unless you pass --force; use --no-install to scaffold without running the package manager.

Next.js on Vercel with a local link

A pnpm link: dependency points at a path on your workstation. That checkout does not exist on Vercel, so a production build must resolve neither react-uidevkit nor react-uidevkit/server from the dangling link. Linked mode scaffolds production stand-ins and ambient declarations while keeping the real package available in local development:

npx react-uidevkit init --framework next --linked ../react-devkit --no-install

This adds the link: devDependency and generates:

  • devtools/uidevkit-stub.tsx — a null-rendering production component
  • devtools/uidevkit-stub-server.ts — a production handler that always returns 404
  • types/react-uidevkit.d.ts — ambient types so TypeScript does not need the linked checkout on CI

The CLI prints the root-layout mount and the complete next.config.ts aliases. The important shape is a production-only Turbopack alias plus exact-match webpack aliases (the /server key comes before the bare package key), and a dev-only widened Turbopack root — Turbopack refuses to resolve files outside its root, so without it the symlink into the sibling checkout fails with "Module not found":

import path from "node:path";

const isProd = process.env.NODE_ENV === "production";

const nextConfig = {
  turbopack: {
    // Dev only: widen the root so Turbopack can read through the node_modules
    // symlink into the sibling checkout (module-not-found otherwise).
    root: isProd ? undefined : path.resolve(".."),
    resolveAlias: isProd
      ? {
          "react-uidevkit": "./devtools/uidevkit-stub.tsx",
          "react-uidevkit/server": "./devtools/uidevkit-stub-server.ts",
        }
      : undefined,
  },
  webpack: (config) => {
    if (isProd) {
      config.resolve.alias = {
        ...config.resolve.alias,
        "react-uidevkit/server$": path.resolve("./devtools/uidevkit-stub-server.ts"),
        "react-uidevkit$": path.resolve("./devtools/uidevkit-stub.tsx"),
      };
    }
    return config;
  },
};

Regenerate stubs and declarations after public API changes with npx react-uidevkit init --force --linked ../react-devkit --no-install.

react-uidevkit ships two entry points:

| Import | Contents | | ------------------------- | ------------------------------------------------------------------------ | | react-uidevkit | UIDevkit (named + default export), the label helpers, and all types | | react-uidevkit/server | The dev-queue handler (devQueueHandler, POST, createDevQueueHandler) |


Dev variant

The dev loop needs two things: a resource route that receives captures and writes them to the local queue, and the overlay mounted behind a dev guard.

React Router (framework mode)

Add a resource route whose action forwards the request to the bundled handler:

// app/routes/api.uidevkit.ts
import { devQueueHandler } from "react-uidevkit/server";

export const action = ({ request }: { request: Request }) =>
  devQueueHandler(request);

Register it in your route config, then mount the overlay guarded by import.meta.env.DEV so the bundler tree-shakes it out of production builds:

import { UIDevkit } from "react-uidevkit";

export default function Root() {
  return (
    <>
      {/* your app */}
      {import.meta.env.DEV && <UIDevkit endpoint="/api/uidevkit" />}
    </>
  );
}

Pick an endpoint the router actually serves. In framework mode the overlay must POST to a path your app owns — not one your dev server proxies to a separate backend. If, say, /api/* is proxied elsewhere, mount the resource route (and point endpoint) at a path that stays in the React Router app.

Next.js (App Router)

Re-export the handler as a route, and mount the overlay behind NODE_ENV:

// app/api/uidevkit/route.ts
export { POST } from "react-uidevkit/server";
// app/layout.tsx
import { UIDevkit } from "react-uidevkit";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        {process.env.NODE_ENV !== "production" && <UIDevkit />}
      </body>
    </html>
  );
}

The default endpoint is /api/uidevkit, which matches the app/api/uidevkit/route.ts location above. If you move the route, set endpoint to match.

Customizing the dev-queue handler

devQueueHandler / POST are the ready-made handler with defaults. To change the queue directory, host allow-list, or size caps, build one with the factory:

import { createDevQueueHandler } from "react-uidevkit/server";

export const POST = createDevQueueHandler({
  extraHosts: [/\.trycloudflare\.com$/], // beyond the built-in loopback + ngrok hosts
  // queueDir, maxBodyBytes, maxImageBytes, maxQueueEntries also available
});

| Option | Default | Effect | | ----------------- | ---------------------------------- | ---------------------------------------------------------- | | queueDir | .claude/react-uidevkit/queue | Where capture folders are written (the /uidevkit command reads the same path) | | maxBodyBytes | 25 * 1024 * 1024 (25 MB) | Max accepted request body, enforced on raw bytes | | maxImageBytes | 20 * 1024 * 1024 (20 MB) | Max accepted size of a single decoded image | | maxQueueEntries | 200 | Max capture folders before new captures are rejected (429) | | extraHosts | [] | Extra Host values (strings match case-insensitively; RegExps test the hostname) beyond loopback and *.ngrok(-free).(app\|io) |

Out of the box the handler accepts localhost, 127.0.0.1, ::1, and *.ngrok(-free).(app|io) — so you can tunnel your dev server through ngrok and capture straight from your phone.

The /uidevkit command

The package bundles the slash-command template at templates/uidevkit.md. Copy it into your project so Claude Code picks it up:

cp node_modules/react-uidevkit/templates/uidevkit.md .claude/commands/uidevkit.md

Then, after capturing one or more issues, run /uidevkit in Claude Code. It reads each queued bundle in capture order, maps it to the source (owner component chain → grep; DOM target; renderStack; and the route from url/path), applies the fix, and deletes the entry. Pass extra instructions inline — e.g. /uidevkit just tell me the files to report without editing. .claude/react-uidevkit/ is dev-only; add it to .gitignore.


QA variant

In the QA loop the overlay POSTs to your own authenticated API rather than the local queue, so testers can file issues from production. Set variant="qa" (which adds the severity + category pickers) and point endpoint at your API:

import { UIDevkit } from "react-uidevkit";

// Render only for users your app has authorized to file QA reports.
{user?.canFileQa && (
  <UIDevkit
    variant="qa"
    endpoint="/api/qa/uidevkit"
    headers={{ Authorization: `Bearer ${token}` }}
    credentials="include"
    reporter={{ id: user.id, name: user.name, email: user.email }}
    app={{ version: BUILD_VERSION, commit: BUILD_SHA, env: "production" }}
    locale={user.locale}          // "en" | "es" (regional tags fall back, e.g. "es-CO" → es)
    onSubmitted={({ id }) => track("qa_report", { id })}
  />
)}
  • Mount it only for authorized users. Unlike the dev loop, there is no NODE_ENV guard — gate the component on your own authorization check.
  • Credentials / headers. Use headers for a bearer token (or any auth header) and credentials ("include" for cross-site cookies) to authenticate the tester against your API.
  • reporter / app. Attach who filed the capture and which build it came from; both ride along in every bundle.

What your server must implement

Your endpoint receives the CaptureBundle JSON (schema v2 — see src/types.ts for the authoritative shape; the field table is below), stores it (the images arrive as base64 data: URLs in screenshot / regionImage), and responds with an envelope the overlay understands:

// success
{ "ok": true, "data": { "id": "1234" } }
// failure
{ "ok": false, "error": { "code": "unauthorized", "message": "not allowed" } }

On success the overlay reads data.id and calls onSubmitted({ id, bundle }); on failure it shows error.message (falling back to error.code). A bare-string error is also accepted.

Any backend works. The bundle is plain camelCase JSON over fetch, so implement the endpoint in whatever stack you already run. (The author pairs it with a small Clojure API that validates the tester's session and stores each submission — bundle plus decoded images — in Postgres.) Whatever you use, decode and re-validate the images server-side; see Security.


Props

UIDevkit takes all-optional props (from src/types.ts → UIDevkitProps):

| Prop | Type | Default | Description | | ------------------------- | ------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------- | | variant | "dev" \| "qa" | "dev" | "dev" queues locally and points the toast at /uidevkit; "qa" adds severity + category pickers and confirms the submission | | position | "bottom-left" \| "bottom-right" \| "top-left" \| "top-right" | "bottom-right" | Corner the controls + toast dock to | | endpoint | string | "/api/uidevkit" | URL captures POST to | | headers | Record<string, string> | — | Extra headers on the submit request (e.g. an auth header) | | credentials | RequestCredentials | "same-origin" | fetch() credentials mode for the submit request | | app | AppInfo ({ version?, commit?, env? }) | — | Build/deploy identity attached to every capture | | reporter | Reporter ({ id?, name?, email? }) | — | The logged-in user filing the capture, attached to every capture | | locale | string | "en" | UI language. Built-ins: "en" and "es"; regional tags fall back on the primary subtag ("es-CO" → es) | | labels | Partial<Labels> | — | Override any built-in label (merged over the locale's dictionary) | | frameworkComponentNames | string[] | — | Extra component names to treat as framework wrappers — demoted in the owner chain so your components surface first | | onSubmitted | (info: { id?, bundle }) => void | — | Called after a capture is accepted by the server |

Capture bundle

The JSON POSTed on submit (src/types.ts → CaptureBundle, schemaVersion: 2):

| Field | Type | Notes | | ------------------ | ----------------------------- | ---------------------------------------------------------------- | | schemaVersion | 2 | Payload schema version | | mode | "element" \| "region" | Which capture mode produced this bundle | | url / path | string | Full URL and pathname + search | | title | string | document.title | | instruction | string \| null | What the user typed in the note box | | severity | "low" \| "medium" \| "high" \| "critical" \| null | Set in the qa variant only | | category | "bug" \| "ui-fix" \| "content" \| "suggestion" \| null | Set in the qa variant only | | capturedAt | string | ISO timestamp | | viewport | { w, h, dpr } | Viewport size and device pixel ratio | | forcedViewport | "mobile" \| "tablet" \| "desktop" \| null | Active overlay viewport preset, or null for Auto | | userAgent | string | navigator.userAgent | | locale | string \| null | The locale prop, or navigator.language | | app | AppInfo \| null | From the app prop | | reporter | Reporter \| null | From the reporter prop | | target | TargetInfo | Element mode only — tag, id, classes, text, selector, rect, outerHTML, computed styles, aria role | | componentChain | ComponentChainEntry[] | Element mode only — React owner components, nearest first (framework flags wrappers) | | renderStack | string[] | Element mode only — raw React render frames (secondary hint) | | region | { x, y, w, h } | Region mode only — the box's pixel coords | | elementsInRegion | RegionElement[] | Region mode only — sampled (not exhaustive) components in the box, capped at 12 | | recentConsole | ConsoleEntry[] | Console errors/warnings buffered before the capture | | screenshot | string \| null | Base64 PNG data-URL (region mode: the full page with the box outlined) | | regionImage | string \| null | Region mode only — a cropped PNG close-up of the box |


Security

The dev-queue handler is defense-in-depth so a misconfigured preview can't become an unauthenticated file-write sink:

  1. Mount the overlay behind a dev guard (import.meta.env.DEV / process.env.NODE_ENV !== "production") so it's tree-shaken out of production.
  2. The handler returns 404 when NODE_ENV === "production".
  3. It rejects any request whose Host isn't loopback or an allow-listed tunnel (404).
  4. It rejects cross-origin writes — it requires Sec-Fetch-Site: same-origin (falling back to an Origin/Host host match), so another site you have open can't drive-by POST into your queue (403).
  5. Request bodies are capped in bytes before parsing (25 MB default → 413), embedded images are validated by magic bytes and capped separately (spoofed or oversized images are dropped, not written), and the queue is bounded so a flood can't fill the disk (429).

For the QA variant the same principles apply, but the trust boundary is your server, not this handler:

  • Authenticate the tester server-side. The overlay attaches whatever headers/credentials you configure, but your API must verify the session/role before accepting a submission — never trust the reporter field as identity.
  • Cap sizes on the request body and each decoded image, and re-validate image magic bytes server-side before persisting — the same PNG/JPEG/WEBP checks the dev handler does (decodeImage in src/server.ts is a reference implementation). Never write client-supplied bytes to disk under an image name without checking them.

Caveats

The single round FAB is used on every form factor; its actions and viewport presets stay hidden until the FAB is opened.

  • WebGL / <canvas> content renders blank in the screenshot — html-to-image can't read back GPU pixels without preserveDrawingBuffer. The DOM context is still captured.
  • Region capture is viewport-only. Area mode snapshots the current viewport and locks body scroll while active; content scrolled out of view isn't captured.
  • Highlight boxes can drift mid-pinch-zoom — browsers disagree on tracking position: fixed during a pinch; the overlay re-aligns when the gesture settles.
  • ⌘/Ctrl+Shift+S collides with browser/OS "save" shortcuts. The ⬚ Area button is the reliable, discoverable path.
  • elementsInRegion is sampled, not exhaustive — it hit-tests a grid of points across the box and dedupes, capped at 12, so small or occluded elements between sample points are omitted.

Requirements

  • React >=18 and react-dom >=18 (the owner-chain walk is richest on React 19).
  • html-to-image >=1.11 — an optional peer dependency that enables the in-browser screenshot. Captures still work without it; you just get the context bundle (owner chain, DOM, styles, console) with no image.
  • Node >=18 for the dev-queue handler (it uses the standard Fetch Request/Response).

License

MIT © Luis Fernando Lara Saldarriaga