DOM Inspector
A lightweight browser-side DOM inspector library for selecting an element and returning stable selector, XPath, JS path, iframe, shadow DOM, and pseudo-element information.
Demo
Workspace
packages/dom-inspector: TypeScript library.apps/web: Vite + React + TypeScript demo page.
Package Installation
After the package is published to npm, install it with your package manager:
npm install @hereorcode/dom-inspector
pnpm add @hereorcode/dom-inspector
yarn add @hereorcode/dom-inspector
bun add @hereorcode/dom-inspector
Local Setup
This package has not been published to npm yet. Use the workspace package while developing locally.
pnpm install
pnpm dev
The demo app runs through @hereorcode/dom-inspector as a workspace dependency.
Open the local Vite URL printed by pnpm dev, start the inspector, then click
sample elements to inspect their generated paths.
Library Usage
Inside this workspace, import the package by its package name:
import { createDOMInspector } from "@hereorcode/dom-inspector";
const inspector = createDOMInspector({
selectionScope: { modifierKey: "Alt" },
onSelect(result) {
console.log(result.selector);
console.log(result.xpath);
console.log(result.jsPath);
}
});
inspector.start();
Hold Alt/Option while scrolling to expand or shrink the current hover scope
before clicking. Pass selectionScope: false to disable this behavior.
Call stop() to remove active listeners and overlays without destroying the
instance. Call destroy() when the inspector is no longer needed.
The selected result includes:
type DOMInspectResult = {
element: Element;
ownerDocument: Document;
rootKind: "document" | "iframe" | "shadow";
selector: string;
selectorWithPseudo: string;
xpath: string;
fullXPath: string;
jsPath: string;
framePath: DOMInspectorFramePath[];
shadowPath: DOMInspectorShadowPath[];
pseudoElement: "::before" | "::after" | null;
pseudoElements: PseudoElementInfo[];
pseudoElementRect: RectLike | null;
rect: DOMRect;
event: MouseEvent;
};
Example output:
selector: body > main > div > div > div:nth-child(2)
xpath: /html/body/main/div/div/div[2]
jsPath: document.querySelector("body > main > div > div > div:nth-child(2)")
Local Package Development
Build the library before consuming its dist output from another local project:
pnpm --filter @hereorcode/dom-inspector build
Until the package is published, use a workspace dependency, pnpm link, or a
local file dependency from the consuming project. After publishing, this section
can switch to the normal published-package installation flow.
Edge Cases
- Same-origin iframes are inspected recursively. For elements inside a frame,
selectorandxpathare local to that frame document, whilejsPathincludes the frame hop from the top-level document, for example:
document.querySelector("body > iframe").contentDocument.querySelector("body > main > button")
Cross-origin iframes cannot expose their internal DOM to page JavaScript. Browser security only allows selecting the
<iframe>element itself.Open Shadow DOM is supported through event
composedPath(). The returnedjsPathincludes.shadowRoot.querySelector(...). Closed Shadow DOM cannot expose internal elements to external code, so the host element is selected.Same-origin iframes and open Shadow DOM can be combined. The returned
jsPathcomposes both hops, for example:
document.querySelector("body > iframe").contentDocument.querySelector("body > main > div").shadowRoot.querySelector("button")
- Pseudo-elements such as
::beforeand::afterare not real DOM elements. The library returns the owning element, lists visible pseudo-elements inpseudoElements, and setspseudoElementplusselectorWithPseudowhen a positioned pseudo-element's visible region is hit.
Development
pnpm install
pnpm dev
pnpm typecheck
pnpm test
pnpm build
Repository Maintenance
Use scripts/reset-repo-history.sh only when intentionally resetting the
repository to a single fresh commit. The script deletes existing tags, creates a
new initial commit from the current files, recreates the requested tag, and can
force-push the target branch and tag to the remote.
Always preview the plan first:
scripts/reset-repo-history.sh --tag v0.1.0 --dry-run
Run the reset only from a clean working tree:
scripts/reset-repo-history.sh --tag v0.1.0 --execute
The script creates a git bundle backup by default before rewriting history.
Remote branch protection can block the push, and GitHub Releases may need to be
deleted manually after old tags are removed.
