Trace your Svelte components in the DOM—effortlessly.
A Svelte 5 preprocessor that injects data attributes into DOM elements for reliable tooling and debugging.
When building devtools, visual editors, or automation around Svelte, you constantly need to answer: which part of the source does this DOM node belong to?
Svelte Trace is a preprocessor that answers that automatically. It tags every element in the compiled markup so tools can read its file path, line, and column—plus a stable short ID—directly from the DOM, without maintaining fragile mappings by hand.
data-svelte-trace encoding file, line, column, and offset metadata.data-svelte-trace-id so they can be re-selected after re-renders or HMR.svelte.config.js; no extra wiring needed for the core tagging.trace-ready notifications, and programmatic element-click commands are all supported.npm install svelte-trace --save-dev
svelte.config.jsImport and add svelteTrace to your preprocessors array:
import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { svelteTrace } from "svelte-trace";
const config = {
preprocess: [vitePreprocess(), svelteTrace()],
kit: { adapter: adapter() },
};
export default config;
All options are optional. Defaults: openInEditor: "", showIndicator: true, postToParent: true.
// Use defaults
svelteTrace();
// Open source in VS Code on Ctrl+Click
svelteTrace({ openInEditor: "vscode" });
// Open in Cursor, hide overlays, keep postMessage
svelteTrace({ openInEditor: "cursor", showIndicator: false, postToParent: true });
| Option | Type | Default | Description |
|---|---|---|---|
openInEditor |
"vscode" | "cursor" | "" |
"" |
Enables Ctrl+Click to open the element's source in the chosen editor. |
showIndicator |
boolean |
true |
When true, shows hover and click overlays on traced DOM elements. |
postToParent |
boolean |
true |
When true and inside an iframe, posts indicator-click, indicator-hover, and trace-ready to window.parent. Set to false to disable outbound posts while keeping overlays. The element-click listener is always registered when the page is embedded. |
npm run dev
Svelte Trace only runs during development; it does not affect production builds.
Svelte Trace injects two data attributes onto every element at compile time:
Before preprocessing:
<div class="container">
<h1>Hello World</h1>
</div>
After preprocessing:
<div class="container" data-svelte-trace="..." data-svelte-trace-id="st_82hj23af">
<h1 data-svelte-trace="..." data-svelte-trace-id="st_ab1234ef">Hello World</h1>
</div>
| Attribute | Contents |
|---|---|
data-svelte-trace |
Base64-encoded string containing source location metadata (file path, line, column, offsets). |
data-svelte-trace-id |
A short, stable ID (e.g. st_82hj23af) derived deterministically from filePath + ":" + tagOffsetStart. |
The ID stays stable across recompiles as long as the element's position in the file does not change. If a hash collision occurs within the same document, Svelte Trace automatically extends the hash to ensure uniqueness.
const el = document.querySelector("[data-svelte-trace]");
const decoded = atob(el.getAttribute("data-svelte-trace"));
console.log(decoded);
const encoded = "dGFnWzI6M10tY2xhc3NbLTE6LTFdLWZbL3BhdGgvc3ZlbHRlXQ==";
const decoded = Buffer.from(encoded, "base64").toString("utf8");
console.log(decoded);
The decoded string follows this structure:
tagName[...]-tagLineCol[...]-tagOffset[...]-classOffset[...]-file[...]
| Field | Example | Description |
|---|---|---|
tagName[name] |
tagName[h1] |
The element's tag name. |
tagLineCol[line:col] |
tagLineCol[44:4] |
Line and column of the opening tag. Used by editors to jump to the right position. |
tagOffset[start:end] |
tagOffset[1200:1220] |
Character offsets of the opening tag's content—the span between < and >. For <h1 class="flex">, this covers h1 class="flex". |
classOffset[start:end] |
classOffset[1263:1350] |
Character offsets of the class attribute value. Returns -1:-1 only when no class attribute is present. An empty (class="") or bare (class) attribute still returns real offsets. |
file[path] |
file[/src/App.svelte] |
Absolute path to the source file. |
Enable Ctrl+Click to open an element's source directly in your editor:
svelteTrace({ openInEditor: "vscode" });
svelteTrace({ openInEditor: "cursor" });
Use this when your Svelte app runs inside an iframe—for example, in a visual editor or design tool—and the parent page needs to:
tagOffset, classOffset, etc.) after each compileCommunication across the iframe boundary uses postMessage in both directions. Because DOM nodes cannot be passed across the boundary, elements are identified by their id or data-svelte-trace-id.
trace-ready (iframe → parent)Purpose: Notifies the parent that trace-bearing markup in the DOM may have changed. Any cached tagOffset or classOffset from a previous compile should be treated as stale until the next indicator-click (or your own re-parse).
Requirements: postToParent: true and the page must be embedded (window.parent !== window).
When it fires:
window load event (or immediately if the document is already complete when the script initializes)MutationObserver activity: childList or subtree changes under document.documentElement, or attribute changes on data-svelte-trace / data-svelte-trace-idPayload:
{ type: "trace-ready", source: "svelte-trace" }
Note: There is no debounce. A single HMR pass can produce multiple
trace-readymessages. The parent should coalesce them if needed (e.g. withrequestAnimationFrame).
element-click (parent → iframe)Purpose: Instructs the iframe to programmatically click a specific element, following the same code path as a real user click—including overlays (if enabled) and outbound indicator-click (if postToParent is true).
Payload:
| Field | Type | Description |
|---|---|---|
type |
"element-click" |
Required. |
id |
string | null |
The element is located via document.getElementById(id) first. |
traceId |
string | null |
Used when id is null or empty. Looks up [data-svelte-trace-id="…"] via CSS.escape. |
Resolution order:
id → getElementByIdtraceId → querySelector on data-svelte-trace-idExamples:
// Locate by DOM id
iframe.contentWindow.postMessage(
{ type: "element-click", id: "hero", traceId: null },
targetOrigin,
);
// Locate by trace id
iframe.contentWindow.postMessage(
{ type: "element-click", id: null, traceId: "st_abcd1234" },
targetOrigin,
);
Sent only when postToParent: true and the page is embedded. Every message includes source: "svelte-trace".
type |
When it fires | Extra fields |
|---|---|---|
trace-ready |
On load and on trace-related DOM mutations | (none) |
indicator-click |
User click, or a successful element.click() triggered by element-click |
rect, element |
indicator-hover |
Pointer moves over a traced element | rect, element |
The element object on click and hover events:
| Field | Present when |
|---|---|
tagName, id, className, traceId |
Always |
tagLineCol, tagOffset, classOffset, file |
When data-svelte-trace decodes successfully |
traceId is taken from the nearest [data-svelte-trace] ancestor's data-svelte-trace-id.
The indicator bundle is injected into the root layout whenever any of the following is true: openInEditor is "vscode" or "cursor", showIndicator is true, or postToParent is true.
Before indicator.js runs, these globals are set:
| Global | Controls |
|---|---|
window.__SVELTE_TRACE_SHOW_INDICATOR__ |
Whether overlay UI is rendered |
window.__SVELTE_TRACE_POST_TO_PARENT__ |
Whether outbound postMessage events are sent |
To tear down Svelte Trace at runtime (disconnect observers, remove listeners):
window.__SVELTE_TRACE_DESTROY__?.();
Issues and pull requests are welcome on GitHub.
MIT — see LICENSE.
Built with ❤️ for the Svelte community.