react-inspect-overlay
v0.1.5
Published
A Vue-DevTools-style component inspector for React — hover the page to highlight components, click to jump to their source. Works with both legacy ReactDOM.render and React 18/19 createRoot.
Maintainers
Readme
react-inspect-overlay
A Vue-DevTools-style component inspector for React. Drop one component into your app, click the floating button, then hover the page — every React component highlights with its box model, name and size. Click one to jump straight to its source in your editor.
- 🔍 Inspect only — no panels, no component tree, just the thing you reach for most: "which component is this, and where does it live?"
- 📦 Box-model overlay — margin / border / padding / content layers, just like Chrome and Vue DevTools.
- ⌨️ Keyboard navigation — walk the component tree with
↑/↓, freeze the highlight withSpace, all without leaving inspect mode. - ⚛️ Animated launcher — a draggable React-atom button that orbits while idle and accelerates when inspect mode is armed.
- 🎯 Click to open in editor — jumps to the exact JSX line and auto-detects your running editor (VS Code, Cursor, WebStorm, Sublime …). Works with Vite and Create React App / CRACO, on the web and inside Electron.
- 🧩 Works with both React architectures — legacy
ReactDOM.renderroots and moderncreateRoot/hydrateRoot(React 16.8 → 19). - 🛡️ Shadow-DOM isolated — the UI lives in its own shadow root, so the host app's CSS can never touch it and vice versa.
- 🪶 Zero CSS dependency — fully self-contained inline styles.
Install
npm install -D react-inspect-overlay
# or
pnpm add -D react-inspect-overlay
# or
yarn add -D react-inspect-overlayHow it works (the mental model)
The inspector is made of two independent pieces. Understanding them makes every setup below obvious:
The runtime overlay —
<Inspector />. A normal React component you mount once. It draws the highlight, reads React's fiber data to find component names, and on click resolves the picked component's source location (file:line:column).The source location. The browser doesn't know which file a
<div>came from — that information only exists at build time. So a build-time plugin stamps every JSX element with adata-source-loc="file:line:col"attribute. At pick time the overlay reads it back and asks the dev server to open that file in your editor.
So "open in editor" needs two things wired up: a build plugin (to stamp the location) and a dev-server endpoint (to actually launch the editor). Which plugin/endpoint you use depends on your bundler — that's the only thing that changes between the setups below.
JSX ──build plugin──► <div data-source-loc="src/App.tsx:12:5">
│ (click)
<Inspector> reads the attribute
│ fetch()
dev server ──launch-editor──► VS Code opens App.tsx:12Mount <Inspector /> the same way everywhere:
import { Inspector } from "react-inspect-overlay";
createRoot(document.getElementById("root")!).render(
<>
<App />
<Inspector />
</>,
);<Inspector /> disables itself when NODE_ENV === "production", so it is safe
to leave mounted, and it marks its own fibers as internal so it never inspects
itself. Everything below is just "how do I wire the build plugin for my
bundler".
Setup by environment
A) Vite (web app)
Add the Vite plugin — it does both jobs (stamps JSX through its Babel pass and serves the open-in-editor endpoint):
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { reactInspector } from "react-inspect-overlay/vite";
export default defineConfig({
plugins: [reactInspector(), react()],
});Plugin order. Keep
reactInspector()before your React plugin.@vitejs/plugin-react-swc(and other JSX compilers) erase JSX before any later plugin can read it, so they must run afterreactInspector(). With@vitejs/plugin-react(the Babel variant) the order is corrected automatically — the inspector injects through its Babel pass — but a wrong order with the SWC variant is reported on startup so it never fails silently.
What happens: the plugin tags JSX during dev, and registers
GET /__react-inspector/open on Vite's dev server. The overlay fetches that URL
on click, and launch-editor opens the file. Done.
Using a CommonJS vite.config.js (project without "type": "module")? It
still works — the package ships a CommonJS build, so require() resolves
without renaming the file to .mjs:
// vite.config.js (CommonJS)
const { reactInspector } = require("react-inspect-overlay/vite");
module.exports = { plugins: [reactInspector(), require("@vitejs/plugin-react")()] };B) Create React App / CRACO (web app)
CRA has no Vite plugin pipeline, so wire the two pieces through CRACO:
- Stamp JSX with the standalone Babel plugin (
react-inspect-overlay/babel). - Serve the open-in-editor endpoint with the bundled dev-server middleware
(
react-inspect-overlay/middleware).
// craco.config.js (CommonJS — no need to rename to .mjs)
const isDev = process.env.NODE_ENV === "development";
const { reactInspectorBabel } = require("react-inspect-overlay/babel");
const { reactInspectorMiddleware } = require("react-inspect-overlay/middleware");
module.exports = {
babel: {
plugins: isDev ? [reactInspectorBabel] : [],
},
devServer: (config) => {
if (isDev) {
const prev = config.onBeforeSetupMiddleware;
config.onBeforeSetupMiddleware = (server) => {
server.app.use(reactInspectorMiddleware());
if (typeof prev === "function") prev(server); // keep CRA's middlewares
};
}
return config;
},
};What happens: CRACO injects the Babel plugin into CRA's babel-loader, so
your JSX gets data-source-loc tags; the middleware answers
GET /__react-inspector/open and launches your editor with launch-editor
— resolved from inside this package, so you never add it as a direct
dependency (this matters under pnpm, which doesn't hoist transitive deps). The
isDev guard and the Babel plugin's own production no-op keep all of this out
of shipped builds.
The overlay also falls back to CRA's built-in
GET /__open-stack-frame-in-editor, but whether that route is mounted varies across CRA / CRACO versions — the bundled middleware makes "open in editor" deterministic.
C) Electron + Vite (electron-vite / vite-plugin-electron)
Identical to (A) — there is nothing Electron-specific to do.
// vite.config.ts (renderer config)
import { reactInspector } from "react-inspect-overlay/vite";
import react from "@vitejs/plugin-react";
export default { plugins: [reactInspector(), react()] };Why it just works: in dev, Electron loads the renderer from the Vite dev
server (win.loadURL("http://localhost:5173")). So the page's origin is the
dev server — the overlay's fetch("/__react-inspector/open") reaches Vite
directly. No IPC, no main-process code, no extra config.
D) Electron + CRA / CRACO
Identical to (B) — use the exact same craco.config.js (Babel plugin +
middleware), and load the renderer from the CRA dev server in development:
// main process (dev): load the renderer from the CRA dev server
win.loadURL("http://localhost:31001");Why it just works: the renderer's origin is the CRA dev server, so the
overlay's request to /__react-inspector/open lands on the middleware you
mounted in craco.config.js. The editor launches from the dev server's Node
process — your machine — exactly where you want it. No IPC bridge required.
If you ever load the renderer from a
file://build instead of the dev server, there is no dev server to ask —<Inspector />still highlights and logs to the console, it just can't open files. That's expected; inspecting is a dev-time activity.
Setup at a glance
| Environment | Build plugin | Opens editor via |
| ---------------------- | ----------------------------- | ----------------------------------- |
| Vite (web) | react-inspect-overlay/vite | built into the Vite plugin |
| CRA / CRACO (web) | react-inspect-overlay/babel | react-inspect-overlay/middleware |
| Electron + Vite | react-inspect-overlay/vite | Vite plugin (renderer = dev server) |
| Electron + CRA / CRACO | react-inspect-overlay/babel | react-inspect-overlay/middleware |
Without any build plugin the inspector still works — it shows component names and logs the picked element to the console; it just can't open files.
ESM & CommonJS. The package ships both builds, with import and require
export conditions for every entry (., /vite, /babel, /middleware). A
CommonJS craco.config.js or vite.config.js can require() it without
switching the file to .mjs.
How to use it
- Click the floating React-atom button (or press Alt+Shift+C).
- Move the cursor over the page — components highlight with their box model.
- While inspecting:
- click a component to open its source,
- ↑ / ↓ walk to the parent / child component,
- Space freeze the highlight (so you can read the label),
- Esc or right-click to cancel.
- Drag the button to reposition it against any edge.
API
<Inspector />
| Prop | Type | Default | Description |
| --------- | ---------------------------- | -------------- | ----------------------------------------------------------------- |
| onPick | (pick: PickResult) => void | open-in-editor | Override what happens when a component is picked. |
| accent | string | "#61dafb" | Accent colour for the launcher and highlight. |
| enabled | boolean | auto | Force on/off. Auto = enabled unless NODE_ENV is "production". |
PickResult is { element, componentName, tagName, source }.
reactInspector(options?) — react-inspect-overlay/vite
The Vite plugin (build tagging and the dev-server endpoint in one).
| Option | Type | Default | Description |
| -------- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| editor | string | auto-detect | Editor to open files in. Omit to auto-detect the running editor (VS Code, Cursor, WebStorm, Sublime, Vim …); set to force one, e.g. "code". |
reactInspectorBabel — react-inspect-overlay/babel
The standalone Babel plugin for non-Vite bundlers (CRA / CRACO, webpack, …).
Add it to your Babel plugins. No-ops when NODE_ENV === "production".
reactInspectorMiddleware(options?) — react-inspect-overlay/middleware
A connect/express-compatible dev-server middleware that serves the
open-in-editor endpoint (GET /__react-inspector/open) for non-Vite setups.
Mount it in development only.
| Option | Type | Default | Description |
| -------- | -------- | ----------- | ------------------------------------------------------------------------------ |
| editor | string | auto-detect | Editor to open files in. Omit to auto-detect; set to force one, e.g. "code". |
Why two architectures?
React exposes its fiber bookkeeping on host DOM nodes under a key whose prefix
changed between major versions — __reactInternalInstance$… (React 16, legacy
ReactDOM.render) and __reactFiber$… (React 17–19, legacy roots and
createRoot). The inspector reads both, so it resolves components no matter how
the host app mounts.
Contact
Author: Qobiljon Jumaboyev
- LinkedIn — qobiljon-jumaboyev
- GitHub — @qobiljonDev
For questions, suggestions or security concerns, reach out through the channels above.
License
MIT © Qobiljon Jumaboyev
