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

@sikka/mugshot

v0.3.1

Published

Screenshot any rendered React component into marketing-ready PNGs.

Downloads

12,252

Readme

@sikka/mugshot

Screenshot any rendered React component into marketing-ready PNGs — as it is: typed values, open dropdowns, arrangement. A literal mugshot, not a re-render. Your components render untouched: no wrapper requirements beyond <Mugshot>, no portal props, no edits to your inputs — popovers, select menus, and calendars from any UI library are captured with their trigger at the right scale.

import { Mugshot } from "@sikka/mugshot";

<Mugshot shotId="upload">
  <YourFileUploader /> {/* the raw component — no cards, no copy */}
</Mugshot>;
mugshot                 # all shots → public/mugshots/<id>.png
mugshot upload --scale 1
import { MugshotCard } from "@sikka/mugshot";

<MugshotCard imageSrc="/mugshots/upload.png" tagline="Drag, drop, done." />;

Install

pnpm add @sikka/mugshot

react >= 18 is the only peer dependency. MugshotStudio needs no Tailwind, no CSS, no config — it is inline-styled and works in any Next.js app (App or Pages Router, app/ or src/app/). MugshotCard and the optional Toolcraft chrome use Tailwind but are never required for capture. Capture runs headless Chromium via playwright-core — no browser config, no canvas libraries. (Excalidraw / tldraw were deliberately NOT used: they are vector-drawing canvases that can't host live DOM and would add megabytes — the pan/zoom is a ~60-line view transform, captures always at true scale.)

pnpm exec playwright-core install chromium --only-shell   # once per machine

Integrate (any Next.js project, 60 seconds)

No Tailwind, no config, no CSS import. Works with app/ or src/app/, App Router or Pages Router.

pnpm add @sikka/mugshot
pnpm mugshot init                 # scaffolds API route + studio route
pnpm exec playwright-core install chromium --only-shell   # once per machine
pnpm dev                          # open http://localhost:3000/dev/shots

mugshot init creates:

  • app/api/dev/mugshot/route.ts — createMugshotHandlers() (snapshot store + capture runner; dev-guarded, never serves prod)
  • app/dev/shots/page.tsx — MugshotStudio starter with a DemoScene — replace with your real components
  • public/mugshots/.gitkeep

Or scaffold a custom route: pnpm mugshot init --route /dev/mugshots

Manual (2 files, same result):

// app/dev/shots/page.tsx
"use client";
import { MugshotStudio } from "@sikka/mugshot";
import { UploadScene } from "./scenes";
const SHOTS = [{ id: "upload", label: "Upload", render: UploadScene }];
export default function Page() {
  if (process.env.NODE_ENV !== "development") return null;
  return <MugshotStudio shots={SHOTS} />;
}
// app/api/dev/mugshot/route.ts  (App Router)
import { createMugshotHandlers } from "@sikka/mugshot/server";
export const { GET, POST } = createMugshotHandlers();
// Pages Router: pages/api/dev/mugshot.ts → export default createMugshotPagesHandler()

Scenes must be deterministic (pinned dates, seeded values) so re-captures are stable — but anything interactive (typed text, open menus, toggles) is captured live, as-is. Scene function identities must stay stable across renders (define SHOTS at module level).

Capture — arrange freely on the infinite canvas, then press Save: a photo frame appears — drag it to reposition, pull its handles to resize — then Take photo captures exactly what's inside the box, as-is (headless @2x). Or headless:

mugshot upload --base-url http://localhost:3000 --route /dev/shots

PNGs land in public/mugshots/ — use <MugshotCard> or your own markup. Guard the studio route with NODE_ENV !== "development" → 404 (handlers already do).

Connect (remote studios)

Any Next.js project can expose its scenes to a remote studio (a Toolcraft app, a dashboard, anything HTTP) — no component source ever leaves the app:

1. Config — only if you need Toolcraft chrome (optional for lightweight studio):

// next.config.ts — skip this for the default lightweight MugshotStudio
import { withMugshot } from "@sikka/mugshot/next";
export default withMugshot(nextConfig);

2. Declare scene settings next to your scenes (keys map 1:1 to real component props):

// app/dev/shots/connect.ts
import type { MugshotConnectScene } from "@sikka/mugshot";

export const CONNECT_SCENES: MugshotConnectScene[] = [
  {
    id: "currency-input",
    label: "Currency input",
    route: "/dev/shots",
    settings: [
      { kind: "boolean", key: "commas", label: "Show commas", defaultValue: true },
      { kind: "boolean", key: "symbol", label: "Show symbol", defaultValue: true },
      { kind: "text", key: "amount", label: "Amount", defaultValue: "12000" },
    ],
  },
];

Setting kinds: select (options + default), boolean, text (default + placeholder), number (default + min/max/step).

3. Read initial values in scenes (stays fully interactive locally):

import { resolveMugshotSettingValues } from "@sikka/mugshot";

const [initial] = React.useState(() =>
  resolveMugshotSettingValues(
    typeof window === "undefined" ? "" : window.location.search,
    CURRENCY_SETTINGS,
  ),
);
// initial.commas / initial.symbol / initial.amount — validated, defaulted

4. Support the transparent embed stage on your studio route (?shot=<id>&mss=<encoded>&mugshot-embed=1):

import { isMugshotEmbedRequest } from "@sikka/mugshot";

if (typeof window !== "undefined" && isMugshotEmbedRequest(window.location.search)) {
  const id = new URLSearchParams(window.location.search).get("shot");
  if (id && id in SCENES) return <div style={{ background: "transparent" }}>{/* bare scene */}</div>;
}

5. Serve the manifest from the API route:

// app/api/dev/mugshot/route.ts
import { createMugshotHandlers } from "@sikka/mugshot/server";
import { CONNECT_SCENES } from "../../dev/shots/connect";

export const { GET, POST } = createMugshotHandlers({ scenes: CONNECT_SCENES });

Remote contract (all dev-guarded):

# list scenes + setting schemas
curl "http://localhost:3000/api/dev/mugshot?manifest=1"
# → { version: 1, shots: [{ id, label, route, settings }] }

# transparent PNG bytes for driven settings (no files written)
curl -X POST http://localhost:3000/api/dev/mugshot \
  -H 'Content-Type: application/json' \
  -d '{"preview":{"shot":"currency-input","route":"/dev/shots","query":"mugshot-embed=1&mss=<encoded>","scale":2}}'
# → { dataUrl: "data:image/png;base64,..." }

Build shareable scene URLs with mugshotSceneUrl(route, shotId, values) from @sikka/mugshot. Screenshots are element captures with omitBackground, so empty areas stay transparent — the remote studio chooses the backdrop.

Studio

<MugshotStudio shots={…} /> is zero-dependency (React only, all styles inline) — no Tailwind, no shadcn, no CSS import needed. It works in any Next.js app, any design system, any CSS setup.

It handles the whole job:

  • infinite canvas (full-screen, sidebar controls): add scenes, drag them anywhere with optional 10px snap, per-item scale, remove — arrangements + photo frame persisted per shot in localStorage;
  • pan/zoom navigation (background drag, hold Space, middle-mouse, scroll to pan, Ctrl+scroll to zoom, Fit button) — pan/zoom is view-only, captures always happen at true scale;
  • photo frame on Save: drag to reposition, pull handles to resize (or type W×H, or Fit-to-content) — Take photo captures exactly what's inside;
  • white / dark / transparent canvas backgrounds;
  • two export paths: instant in-browser Export (HTML-in-canvas, see below) where the browser supports it, plus headless photo capture (@2x via the CLI) that works everywhere — both save into public/mugshots/ with a preview.

Props: canvasSize (default 600 — default frame size + capture fallback), exportSize (default 2048 — native-export long edge), storageKey, endpoint (default /api/dev/mugshot), resolveOutName, previewBase (default /mugshots), targetSuffix (default -capture — the capture box target [data-mugshot="<id>-capture"] must differ from scene ids, since snapshots keep inner <Mugshot> targets verbatim).

Hotkey — capturing open menus (required, not optional): open any dropdown/popover/calendar, keep it open, then press ⌘⇧C (Ctrl+Shift+C on Windows) for Capture or ⌘⇧E for Export. Clicking Take photo can never capture an open menu: floating-UI libraries (Base UI, Radix) cover the page with an invisible dismiss layer the moment a menu opens, so the click lands on that layer and closes the menu before any handler runs. The hotkey serializes the live frame on keydown while the menu is still mounted — no pointer involved — so the menu is cloned glued to its trigger at any item scale. Scene controls stay clickable at all times (items drag only from empty areas; the background pans), so you can always open the menu first, then hotkey.

Next.js helper (optional)

withMugshot merges transpilePackages: ["@sikka/mugshot"] so the package compiles from source; your own config always wins on conflicts:

// next.config.ts
import { withMugshot } from "@sikka/mugshot/next";
export default withMugshot(nextConfig);

Default MugshotStudio needs no config — it uses only React + inline styles and works in any Next.js app out of the box.

How literal capture works

Two paths, same literal-mugshot principle (never re-mount scenes):

Export — instant, in-browser (shown when supportsNativeCapture() passes, i.e. Chrome with chrome://flags/#canvas-draw-element). The live stage nodes move into a hidden layoutsubtree canvas and rasterize via a single drawElementImage(content, 0, 0, 2048, 2048) at full export resolution, then move back — typed values, canvases, and open menus travel with full state. This is the canvas-ui pattern (layoutsubtree + requestPaint → paint + drawElementImage, same support probe, failures fall back to headless capture).

Capture — headless (works in any browser). Instead it:

  1. serializes exactly the live frame region (serializeMugshotRegion) — intersecting items re-based to frame origin with input values, checkboxes, selects, textareas, canvases (signature pads) baked in; open floating menus cloned in place at their trigger's measured scale (works for any portal-based library — Radix, Base UI, Headless UI…);
  2. stores the HTML via the API route, then runs the CLI against ?mugshot-capture=1&snapshot=…&bw=…&bh=…, which renders that HTML verbatim in a frame-sized box (same app CSS, pinned theme) and screenshots exactly that box with animations frozen — a literal crop of the blue frame, so anything overhanging it (menus included) crops at the edge; resize the frame to include what you want.

Snapshots expire after 30 minutes and never touch your repo. Menus that can't be attributed to a canvas item (e.g. your own dev toolbar's popovers) are excluded; viewport-scale modal backdrops are skipped.

The paradigm, stated plainly: the model is always scale 1 and portals always natural — everything renders exactly like normal DOM, so any HTML/React just appears. Pan/zoom are view-only (frame freely, even mid-zoom); arrangement is plain world left/top; anything outside the frame simply crops out of the shot. Per-item scale is there for emphasis, and open menus are re-scaled to their trigger's item scale in the snapshot — dropdowns capture glued at any scale.

Classic registry flow (unchanged)

Deterministic CI-style captures without the studio: register shots ([{ id, label, route, imageSrc }]), render <Mugshot shotId> scenes on a route that reads ?shot=<id>, and run the bin. --overlays adds the union-box pass for scenes that mount already-open (<Select defaultOpen>).

CLI

mugshot init [--route /dev/shots]   # scaffold any Next.js app (App or Pages Router)
mugshot --help
mugshot [shot …] [--base-url …] [--out-dir …] [--scale 2] [--registry …] [--query …] [--target-suffix …] [--route …] [--out-name …] [--overlays]

| Flag | Default | Meaning | | ---------------- | --------------------- | --------------------------------------------------------------- | | shot … | all registered | Capture only these ids | | --base-url | http://localhost:3000 | Dev server origin (must be running) | | --out-dir | <cwd>/public/mugshots | Where <id>.png files are written | | --scale | 2 | Retina multiplier (deviceScaleFactor) | | --registry | packaged registry | JSON file with your shots array | | --query | — | Extra query appended to each shot URL (e.g. square=1&x=0&y=0) | | --target-suffix | — | Suffix for the capture selector ([data-mugshot="<id><suffix>"]) | | --route | registry route | Override the route for this run (e.g. a locale twin) | | --out-name | <id>.png | Override the output filename (single-shot runs only) | | --overlays | off | Union-box capture incl. open floating UI (menus, popovers) |

--query + --target-suffix pair up to shoot alternate compositions. --route + --out-name pair up for locale twins. Each shot is captured from <route>?shot=<id> — if your page shows one scene at a time, read the shot search param and mount only that scene (in Next.js, useSearchParams needs a <Suspense> boundary). Studio snapshot runs need no registry file: with an empty registry, explicit shot ids + --route synthesize entries.

Programmatic use:

import { captureMugshots } from "@sikka/mugshot/cli";

await captureMugshots({
  baseUrl: "http://localhost:3000",
  registry: [{ id: "upload", label: "Upload", route: "/dev/shots", imageSrc: "/mugshots/upload.png" }],
  shots: ["upload"],
});

Chromium

Resolution order: $MUGSHOT_CHROMIUM_PATH → shared ms-playwright cache (newest headless shell first, then full Chromium; win/mac/linux) → Playwright default (errors with install instructions). No download needed if the machine already has Playwright browsers. Keep playwright-core in step with the browser build — version skew causes protocol errors on launch.

Entries

  • @sikka/mugshot — browser-safe: Mugshot, MugshotCard, MugshotStudio (lightweight, zero CSS), serializeMugshotStage, serializeMugshotRegion (+ collectMugshotRegionItems/Overlays), mugshotOverlayScale, captureStageNative, captureRegionNative, supportsNativeCapture, MUGSHOT_EXPORT_SIZE, MUGSHOT_SHOTS (+ types). Never pulls in Node APIs or playwright-core.
  • @sikka/mugshot/cli — Node-only: captureMugshots (+ types). Never import from client components. Includes mugshot init scaffolder.
  • @sikka/mugshot/server — Node-only Next.js route handlers: createMugshotHandlers (App Router), createMugshotPagesHandler (Pages Router) (+ types). Never import from client components.
  • @sikka/mugshot/next — optional withMugshot() helper (only for Toolcraft premium chrome).

License

Proprietary — Sikka Software.