Instant Component Ancestry in Your Clipboard
Alt+click any UI element to copy its complete component tree
TreeLocatorJS is a developer tool for copying component ancestry trees to your clipboard. Hold Alt (or Option on Mac) and click any element in your web application to instantly copy its complete component hierarchy.
Perfect for debugging, documentation, code navigation, and understanding complex component structures.
div in App at src/App.tsx:5
└─ main in Layout at src/components/Layout.tsx:12
└─ section:nth-child(2) in Content at src/components/Content.tsx:8
└─ button#submit-btn.btn.btn-primary in Button at src/components/Button.tsx:15
- One-Command Setup -
npx @treelocator/initauto-configures your project - Framework Agnostic - React, Vue, Svelte, Preact, Solid, and more
- Non-Intrusive - No visual clutter, only a subtle tree icon toggle
- Browser Automation Ready - Programmatic API for Playwright, Puppeteer, Cypress (guide)
- AI/MCP Ready - Built-in Model Context Protocol bridge so AI agents can inspect your running app (MCP setup)
- Style-Aware - Computed styles, matched CSS rules, specificity scoring, and snapshot diffs
- Lightweight - Minimal runtime overhead
- Developer First - Built by developers, for developers
Run the setup wizard from your project root — it detects your stack and configures everything:
npx @treelocator/initNon-interactive (CI, scripts):
npx @treelocator/init --yes
# or the shorter alias:
npx treelocatorjs --yesVerify an existing setup:
npx @treelocator/init --checkWhat the wizard does:
| Step | Vite (React, Vue, Svelte, etc.) | Next.js |
|---|---|---|
| Installs packages | @treelocator/runtime, @treelocator/vite, + babel deps for JSX |
@treelocator/runtime, @locator/webpack-loader |
| Configures build tool | Adds treelocator() to vite.config (+ babel for JSX frameworks) |
Adds webpack loader to next.config |
| Wires up runtime | Auto-injected in dev via Vite plugin — no entry file edit | Creates LocatorProvider and wraps app/layout |
Then start your dev server and Alt+click any element.
If you prefer to set things up yourself:
npm install -D @treelocator/runtime @treelocator/vite
# JSX frameworks (React, Solid, Preact) also need:
npm install -D @locator/babel-jsx @rolldown/plugin-babel @babel/coreVite — add to vite.config.js:
import treelocator from "@treelocator/vite";
import babel from "@rolldown/plugin-babel"; // React/Solid/Preact only
export default defineConfig({
plugins: [
react(),
babel({
plugins: [["@locator/babel-jsx/dist", { env: "development" }]],
}),
treelocator(), // auto-injects runtime in dev — no main.tsx edit needed
],
});Vue and Svelte skip the babel plugin — they only need treelocator().
Next.js — see NEXTJS-SETUP.md.
Or add the runtime manually to your entry file:
import { setup } from "@treelocator/runtime";
if (import.meta.env.DEV) setup();- Hold Alt (or Option on Mac) and click any element
- Or click the tree icon in the bottom-right corner, then click an element
The component ancestry is instantly copied to your clipboard.
Customize the behavior with options:
import { setup } from "@treelocator/runtime";
setup({
adapter: "react", // Framework: "react" | "vue" | "svelte" | "jsx"
hotkey: "alt", // Trigger key: "alt" | "ctrl" | "meta"
enabled: true, // Enable/disable at runtime
});Hold Alt and click any element to instantly copy its ancestry tree. The format is clean and readable:
div in ParentName at src/path/to/Component.tsx:42
└─ ul:nth-child(2) in ListContainer at src/path/to/List.tsx:8
└─ li:nth-child(3)#item-active.row.is-selected in ListItem at src/path/to/Child.tsx:15
Each selector segment carries enough fidelity to round-trip back to the DOM:
:nth-child(n)- Position among siblings of the same type (only when ambiguous)#id- Element ID when present.class1.class2- All classes fromclassListin Component- The innermost named owner from the component tree (anonymous framework wrappers are filtered out automatically, so Next.js App Router internals don't pollute the chain)
A subtle tree icon sits in the bottom-right corner. Click it to activate single-click pick mode, or open the adjacent cog to reach the in-page settings panel:
- Toggle anomaly tracking, visual-diff snapshots, and computed-styles capture on/off
- Tune dejitter sample rate, max recording length, jump/lag thresholds
- Opt into
includeDefaultsfor fuller computed-styles dumps - All settings persist in
localStorageunder__treelocator_settings__and take effect on the next recording — no reload needed
Every Alt+click capture also returns the element's computed styles, grouped by category (layout, typography, colors, etc.) and filtered down to the ~50 properties a human actually cares about. Two noteworthy details:
- Shadow-DOM default probe - Browser defaults are measured inside an isolated Shadow DOM so page CSS (universal selectors,
all:resets) can't poison the baseline. includeDefaultsoption - Ask for a full DevTools-style dump when you want every property, not just the non-default ones.
const styles = window.__treelocator__.getStyles(".hero", { includeDefaults: true });Debug specificity conflicts without opening DevTools. For any element the runtime can list every matched CSS rule along with:
- Selector specificity scores (a, b, c)
- Source location (
file:line:column) when the browser exposes it - Origin — inline,
<style>, or external stylesheet - Cross-browser safe parsing (Chrome and Firefox handle
CSSStyleRuledifferently)
const rules = window.__treelocator__.getCSSRules(".my-class");Persistent baselines that survive reloads. With no tree options, takeSnapshot captures the selected element's computed styles. Add getTree options such as maxDepth to snapshot the source-aware tree rooted at the same selector:
// Computed-style snapshot
window.__treelocator__.takeSnapshot(".hero", "hero-layout");
// ...edit, reload, tweak...
const diff = window.__treelocator__.getSnapshotDiff("hero-layout");
console.log(diff.formatted);
// Source-aware tree snapshot
await window.__treelocator__.takeSnapshot(".hero", "hero-tree", {
maxDepth: 3,
maxNodes: 500,
});
const treeDiff = await window.__treelocator__.getSnapshotDiff("hero-tree");
console.log(treeDiff.formatted);Baselines are immutable — getSnapshotDiff never overwrites them. Stored in localStorage under treelocator:snapshot:<id>, so multiple style and tree snapshots coexist across sessions. Also exposed as MCP tools.
TreeLocatorJS exposes a programmatic API for testing frameworks:
// Get formatted ancestry path for any selector
const path = window.__treelocator__.getPath('button.submit');
// "button#submit-btn.btn.btn-primary in LoginForm at src/components/LoginForm.tsx:23"
// Get raw ancestry data with styles and matched CSS rules
const ancestry = window.__treelocator__.getAncestry(document.querySelector('.my-component'));
// ancestry.computedStyles, ancestry.cssRulesPerfect for E2E tests with Playwright, Puppeteer, Selenium, or Cypress.
- API reference: BROWSER-API.md
- Playwright, extension injection, MCP FAQ: PLAYWRIGHT-AND-AUTOMATION.md — start here if you are looking for a console inject snippet or wondering how automation fits together
TreeLocatorJS ships with a built-in Model Context Protocol integration so AI agents (Claude Code, Cursor, any MCP client) can drive and inspect your running app:
browser runtime ──wss──▶ @treelocator/mcp broker ◀──stdio── AI client
- Runtime opens a session to a local WSS broker (
wss://127.0.0.1:7463/treelocator) with exponential backoff + quiet fallback so offline dev machines don't spam the console. @treelocator/mcphosts both the broker and a stdio MCP server.- AI clients can list live browser sessions, pick one, and call tools against it.
MCP tools exposed:
| Category | Tools |
|---|---|
| Session | treelocator_list_sessions, treelocator_connect_session |
| Inspect | treelocator_get_path, treelocator_get_ancestry, treelocator_get_path_data, treelocator_get_tree, treelocator_query_by_source, treelocator_find_source, treelocator_highlight_source, treelocator_get_styles, treelocator_get_css_rules, treelocator_get_css_report |
| Snapshot | treelocator_take_snapshot, treelocator_get_snapshot_diff, treelocator_clear_snapshot |
| Interact | treelocator_click, treelocator_hover, treelocator_type |
| Debug | treelocator_execute_js, treelocator_get_console |
MCP connects to a browser tab where runtime is already running — it does not inject TreeLocator by itself.
Configure the bridge via setup():
setup({
mcp: {
enabled: true,
bridgeUrl: "wss://127.0.0.1:7463/treelocator",
reconnectMs: 1000, // base for exponential backoff, capped at 5 min
},
});See docs/MCP.md for setup, architecture, and the full tool reference.
Record a short interaction window and automatically surface visual anomalies — jumps, lag, jitter, flicker, layout shifts. Thresholds, sample rate, and max duration are all configurable from the settings panel, and visual-diff snapshots can be toggled independently from the core anomaly tracker.
TreeLocatorJS works seamlessly with modern frameworks:
| Framework | Support | Detection Method |
|---|---|---|
| React | ✅ Full | React DevTools Hook |
| Vue | ✅ Full | Vue DevTools Hook |
| Svelte | ✅ Full | Svelte Component Data |
| Preact | ✅ Full | Preact DevTools Hook |
| Solid | ✅ Full | JSX Source Tracking |
| Next.js | ✅ Full | Webpack Loader (App Router & Pages Router) |
| Other JSX | ✅ Full | Babel Plugin |
Quickly identify which component is rendering unexpected output:
# Alt+click on the problematic element
# Paste in your code editor to jump to the sourceGenerate component hierarchy documentation:
# Click through your UI
# Paste the trees into your docsNavigate large codebases with ease:
# Alt+click any element
# Command+P (or Ctrl+P) in your editor
# Paste the file pathWrite more maintainable E2E tests (runtime must be loaded in the app first — see PLAYWRIGHT-AND-AUTOMATION.md):
await page.waitForFunction(() => typeof window.__treelocator__ !== "undefined");
const path = await page.evaluate(() => {
return window.__treelocator__.getPath('button.submit');
});TreeLocatorJS is a monorepo using:
- pnpm workspaces for package management
- Turborepo for coordinated builds
- Lerna for publishing
Requirements:
- Node.js ≥ 22.0.0
- pnpm 8.7.5+
| Package | Description |
|---|---|
@treelocator/runtime |
Core runtime with Alt+click handler, overlay UI, settings panel, and MCP bridge client |
@treelocator/vite |
Vite plugin — auto-injects runtime in dev (no entry file edit) |
@treelocator/init |
CLI setup wizard (npx @treelocator/init) |
@treelocator/mcp |
Local WSS broker + stdio MCP server for AI agent integration (docs) |
Build-time dependencies:
@locator/shared- Shared TypeScript types and utilities@locator/babel-jsx- Babel plugin for JSX source location tracking@locator/webpack-loader- Webpack loader integration@locator/react-devtools-hook- React DevTools integration
Test apps for all supported frameworks live in apps/:
next-14,next-16- Next.js appsvite-react-*- React with Vitevite-preact-*- Preact with Vitevite-svelte-*- Svelte with Vitevite-vue-*- Vue with Vitevite-solid-*- SolidJS with Vite
E2E tests are in apps/playwright/.
# Install dependencies
pnpm install
# Run all packages in development mode
pnpm dev
# Build all packages
pnpm build# Run all tests
pnpm test
# Watch mode for runtime tests
cd packages/runtime && pnpm test:dev
# E2E tests
cd apps/playwright && pnpm test- Runtime entry:
packages/runtime/src/index.ts→initRuntime.ts - Browser API:
packages/runtime/src/browserApi.ts - Overlay UI:
packages/runtime/src/components/Runtime.tsx(SolidJS) - Tree icon:
packages/runtime/src/assets/tree-icon.png - Framework adapters:
packages/runtime/src/adapters/ - Ancestry formatting:
packages/runtime/src/functions/formatAncestryChain.ts - Shared types:
packages/shared/src/types.ts - Babel plugin:
packages/babel-jsx/src/
- Shadow DOM for style isolation
- SolidJS for reactive overlay UI
- TailwindCSS for styling (compiled to
_generated_styles.ts) - Dynamic imports handle SSR vs browser extension contexts
- Tree icon embedded as data URL in
_generated_tree_icon.ts
TreeLocatorJS is published to npm under the @treelocator scope:
- @treelocator/runtime - Core functionality
- @treelocator/vite - Vite plugin for dev-only runtime injection
- @treelocator/init - CLI setup wizard
Current version: 0.6.0
To publish a new version:
# Update version in lerna.json and package.json files
# Then run:
pnpm build
pnpm lerna publish from-package --yesContributions that keep the project focused and developer-friendly are welcome:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - see LICENSE for details.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Full Documentation