element-source-dev
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
Lightweight alternative to Storybook for HTML, React, Preact, Svelte, Vue, and S...
Devtools for simulcast: a framework-independent inspector for the realtime conne...
Version History
| Version | Released | License |
|---|---|---|
| 0.5.2 | MIT | |
| 0.5.1 | MIT | |
| 0.3.0 | MIT |