element-source-dev

Get the source file location of any DOM element in the browser
590
Install
npm install element-source-dev
Latest Version:0.5.2
License:MIT
Last Updated:Oct 2, 2026
Links: npm
Maintainer: tzdxf

Element Source Dev

Click any DOM element and instantly see its source file location, component name, and full component stack — as a drop-in script for any Vite project.

element-source-dev is an importable library that gives you hover-to-highlight + click-to-inspect for any DOM element, powered by element-source. Supports React, Vue, Svelte, Solid, and Preact in development mode.

Install

pnpm add -D element-source-dev
# or
npm i -D element-source-dev
# or
yarn add -D element-source-dev

element-source and html2canvas are installed automatically as dependencies.

Quick start

// main.ts — your app entry
import elementDev from 'element-source-dev';

elementDev();

elementDev is the createElementDev factory — call it with options, or call the returned controller directly. Press Ctrl+Shift+E (default shortcut) to toggle selection mode, hover to highlight, click an element to inspect it. Press Esc to exit.

import elementDev from 'element-source-dev';

// with options
const dev = elementDev({ shortcut: 'Alt+P' });
dev.activate();
dev.destroy();

You can also start selection mode programmatically from the devtools console:

window.dispatchEvent(new CustomEvent('__es_activate__'));

Options

elementDev(options?: ElementDevOptions): ElementDevController

Option Type Default Description
shortcut string 'Ctrl+Shift+E' Keyboard combo that toggles selection mode. e.g. 'Alt+P', 'Cmd+Shift+E'.
registerShortcut boolean true Register a global keydown listener for the shortcut. Set false if you trigger selection yourself.
initialActive boolean false Start with selection mode already on.

Controller API

elementDev returns a controller:

const dev = elementDev();

dev.activate();    // enter selection mode
dev.deactivate();  // exit selection mode (removes panel + overlays)
dev.toggle();      // flip selection mode
dev.isActive();    // boolean
dev.destroy();     // tear down all listeners + UI

Tree-shaking the production bundle

The controller activates wherever you import it — there is no built-in dev-only guard. To keep element-source and friends out of your production bundle, gate the import yourself with your bundler's dev flag:

if (import.meta.env.DEV) {
  const { default: elementDev } = await import('element-source-dev');
  elementDev();
}

Screenshot

The floating panel has a screenshot button that copies a PNG of the inspected element to the clipboard. It works by re-rendering the element to a canvas via html2canvas — so it photographs the DOM rather than the screen. Cross-origin images, some pseudo-elements, and exotic CSS may not render faithfully; if rendering fails (e.g. tainted canvas), the button reports a failure reason. Copying the image to the clipboard requires a secure context (HTTPS or localhost); when that's unavailable the button shows an error instead of silently failing.

Iframes

The browser extension runs with allFrames: true, so every frame in the tab gets its own controller: hover, select, mark and inline style tweaks all work inside embedded documents. Overlays are drawn into the frame that owns the element, the panel shrinks to fit a small frame, and the layer bar shows an iframe badge naming the host element (iframe#id, iframe "src…", iframe×N for nesting) so a source path from an embedded app isn't mistaken for the surrounding page.

Selection mode is started once, in the top frame, and relayed down the frame tree over postMessage — scripting.executeScript stops at the top frame (allFrames defaults to false, and with activeTab even allFrames: true skips a cross-origin child), and mouse events never cross a frame boundary, so a frame that was never woken up would silently ignore every click inside it. A frame that answers the activation is then skipped by its parent's picker: entering a frame fires a single mouseover on its <iframe> element in the parent and nothing else until the pointer leaves, so the parent would otherwise leave a box around the whole frame while the frame highlights the element under the cursor. An <iframe> whose frame never answers (a cross-origin frame the extension cannot inject into, or a page using the drop-in script) stays pickable as a plain element. A frame that comes up after the session started (a lazy iframe) asks the parent for the current mode, so it joins a running session instead of ignoring every click. Picking an element — and pressing Escape — ends the session in every frame: that signal travels up to the ancestors as well as down, so a frame is never left picking after the user is done. matchOriginAsFallback additionally covers frames that match no URL pattern at all — about:blank, <iframe srcdoc>, data: and blob: (inline previews, sandboxed demos).

The drop-in script is per-document — it only instruments the frame that imports it. To inspect inside an iframe, load it there too (e.g. inject the same script into the iframe's document, or mount it from a page served inside the iframe). Cross-origin iframes you don't control cannot be instrumented at all from outside.

Framework support

Works in dev mode with React, Preact, Vue, Svelte, and Solid via element-source. Source resolution requires your framework's dev build to attach the component metadata that element-source reads.

License

MIT

Related Packages

astrobook

The minimal UI component playground

134,651 279
@storylite/storylite

Lightweight alternative to Storybook for HTML, React, Preact, Svelte, Vue, and S...

10,224 145
@priemskiyyy/simulcast-devtools

Devtools for simulcast: a framework-independent inspector for the realtime conne...

591 2
spotnote

Click a UI element in your Vite dev server, write a note, and copy an AI-agent-r...

0 25
@alexstrnik/claude-devtools

Host server for Claude DevTools Chrome extension

885 11

Version History

Version Released License
0.5.2 MIT
0.5.1 MIT
0.3.0 MIT