Skip to content

Troubleshooting

Find the symptom you are seeing, read the cause, apply the fix. Every entry links to the page that explains the behaviour.

Symptom. The page reloads on every edit, whatever file you touch.

Cause. You edited a module that is not self-accepting: one that never tells Vite it can apply its own updates. main.ts and index.html are the usual ones. The edit walks up to a full reload.

Fix. Edit the component module instead. See Keep state across edits.

The console says “standard accessor decorators”

Section titled “The console says “standard accessor decorators””

Symptom.

Terminal window
[lit-plugin] <my-element>: standard accessor decorators — performing full reload.

Cause. Reactive properties declared with standard TC39 accessor decorators close over private slots created fresh on each class evaluation.

Fix. Use experimentalDecorators: true with useDefineForClassFields: false, or declare static properties. Both patch in place. See Limitations.

Symptom.

Terminal window
[lit-plugin] <my-element>: patching failed: Cannot read private member #count from an object whose class did not declare it — performing full reload.

Cause. Copying a reactive-property accessor onto the canonical class made it read state the old instances do not carry.

Fix. Read the detail after patching failed:; it is the real error. Limitations lists the shapes that produce one.

The console says observedAttributes changed

Section titled “The console says observedAttributes changed”

Symptom.

Terminal window
[lit-plugin] <my-element> changed observedAttributes; the platform registry can't pick this up — reload recommended.

Cause. The platform snapshots observedAttributes when the element is defined. The patch itself succeeded, so nothing reloads, whatever onIncompatible says.

Fix. Reload by hand. The old attribute list stays in effect until you do. See Limitations.

I cannot read the reason before the page reloads

Section titled “I cannot read the reason before the page reloads”

Symptom. A line flashes past and the reload wipes the console.

Cause. The default hmr.onIncompatible is 'reload', which logs and reloads in the same breath.

Fix. Set onIncompatible: 'warn' while you investigate. Note it refuses the patch, so the old template stays on the page. The Components tab and lit_hmr-incompatibilities also keep a record. See Options.

The first edit after editing tsconfig.json reloads

Section titled “The first edit after editing tsconfig.json reloads”

Symptom.

Terminal window
[vite] changed tsconfig file detected: /app/tsconfig.json - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.

Cause. Vite clears its transform cache when a tsconfig changes, so the next edit cannot be patched.

Fix. Nothing. This is expected, and only the first edit pays for it. See How HMR works.

A signal, cache or context key resets on every edit

Section titled “A signal, cache or context key resets on every edit”

Symptom. The component keeps its state, but a shared value goes back to its initial one.

Cause. A hot patch re-executes the edited module, and module-level const declarations are re-created with it.

Fix. Move the signal, cache or context key into a module that holds no component. See Keep state across edits.

Symptom. The task’s result is unchanged after you edit its function.

Cause. Task captures the function at construction time, so the live controller still holds the old one.

Fix. Reload once to pick the new body up. Results survive edits to the rest of the component. See Use signals, context, tasks and the virtualizer.

Editing the parent re-renders the child, or an input loses focus

Section titled “Editing the parent re-renders the child, or an input loses focus”

Symptom. A change to the parent’s header rebuilds a child element below it. The child keeps its count, but focus, listeners added by hand, or a reference you held to the element are gone. With hmr.childState: 'reset', its state resets too.

Cause. Everything in one template literal shares one template object, so editing any part of it invalidates the whole. The child is a new element; the default 'transfer' only carries its reactive properties and #private fields across.

Fix. Put the part you edit often in its own template literal, returned from a helper method. For a child without bindings, hmr.childState: 'reuse' puts the original element back. See Keep state across edits.

Every update throws “Cannot read private member”

Section titled “Every update throws “Cannot read private member””

Symptom. After one edit, every update throws. No [lit-plugin] line, no reload, and the page looks like it worked.

Terminal window
Uncaught TypeError: Cannot read private member #x from an object whose class did not declare it

Cause. render(), updated() or a method reads a native #private field that the dev rewrite did not cover: hmr.privateFields is off, or the module has a decorated private member. The throw lands inside Lit’s asynchronous update.

Fix. Reload to recover. Then turn hmr.privateFields back on, or switch to TypeScript’s private modifier. See Limitations.

A newly added reactive property is undefined

Section titled “A newly added reactive property is undefined”

Symptom. The template reads undefined, and only the terminal shows [vite] (client) [Unhandled rejection] TypeError: Cannot read properties of undefined.

Cause. Field initialisers run in the constructor. Instances that already exist never run the new one, and nothing diagnoses it.

Fix. Reload once after adding a property. See How HMR works.

The first edit after a dev-server restart reloads

Section titled “The first edit after a dev-server restart reloads”

Symptom. Restart the server, edit a component, and the page reloads once.

Cause. The interning map that lets Lit recognise an unchanged template lives in the page, not the server, so a restarted server has no record of it.

Fix. Nothing. Later edits patch normally. See How HMR works.

Symptom. You turned reconnect on and see no difference.

Cause. It cycles disconnectedCallback() and connectedCallback() on live instances, nothing more. A component that does no work there has nothing to re-run.

Fix. Leave it off unless connectedCallback() does real setup. See Keep state across edits.

The console shows nothing on a successful patch

Section titled “The console shows nothing on a successful patch”

Symptom. The edit lands, and the console stays empty.

Cause. Vite logs [vite] hot updated: … at debug level, which Chrome hides unless the console shows Verbose.

Fix. Turn Verbose on, or watch the on-page indicator instead. See Keep state across edits.

Symptom. No dot in the corner of the page.

Cause. hmr is off, hmr.indicator is false, or an env var switched one of them off. The indicator is forced off whenever HMR is.

Fix. Check the Settings tab for the resolved values. See Options.

It pulses cyan and the count does not move

Section titled “It pulses cyan and the count does not move”

Symptom. A calm cyan pulse, with the count unchanged.

Cause. A shared stylesheet was swapped in place. No component re-rendered, so nothing counts.

Fix. Nothing. This is the expected signal for CSS delivered as a shared sheet. See Keep state across edits.

Symptom. The running total resets.

Cause. The page reloaded. Only green pulses advance the count, and a reload zeroes it.

Fix. Find the reload in the group above. See Limitations.

The count went up but my change is not on the page

Section titled “The count went up but my change is not on the page”

Symptom. The indicator counts an update you cannot see.

Cause. onIncompatible: 'warn' refused the patch. The old template stays, and the update still counts.

Fix. Read the can't be hot-patched warning and reload by hand. See Options.

Symptom. TypeScript cannot find a module for ./x.css?css-sheet.

Cause. The plugin’s ambient types are not in scope.

Fix. Add @oddsquad/vite-plugin-lit/client to types in tsconfig.json, keeping vite/client alongside it. See Import queries.

Symptom. Adopters render unstyled, and the console shows [lit-plugin] urlSheet: failed to load ….

Cause. The sheet is fetch-backed. When the request fails or returns an error status, the CSSStyleSheet stays empty (or keeps its last good css after an HMR update).

Fix. Check the Network tab for the stylesheet request. See Share one stylesheet with ?css-sheet.

A flash of unstyled content on first paint

Section titled “A flash of unstyled content on first paint”

Symptom. Components appear unstyled for a moment, then snap into place.

Cause. A shared sheet is fetched, by design, so the first paint can beat it.

Fix. Add a <link rel="preload" as="style"> for it, set cssSheetBuild: 'inline', or inline the shared module. See Choose how to deliver CSS.

Symptom. The package works in your app and ships unstyled to its users.

Cause. A fetched URL has nothing to resolve against in someone else’s build.

Fix. build.lib already inlines the sheet. Without it, set cssSheetBuild: 'inline'. See Share one stylesheet with ?css-sheet.

Symptom. Unexpected token Function("var") during dev or build.

Cause. The configured CSS transformer rejects syntax browsers accept. @property declarations are the common trigger.

Fix. Set cssSheetBuild: 'inline-raw', or import the file with ?raw. Both skip the CSS pipeline. See Share one stylesheet with ?css-sheet.

Symptom. Editing the CSS file changes nothing until you reload.

Cause. The import.meta.hot.accept call is missing, hidden inside a helper, or names a specifier built from a variable. Vite resolves the accepted specifier statically.

Fix. Put the accept call in the module that imports the CSS, spelled with the same literal. See Wire a shared sheet by hand with urlSheet().

Symptom. The <link> element never picks up your edit.

Cause. ?url returns a stable URL, so the browser serves it from cache.

Fix. Import with ?hmr-url, or call devCacheBust() at module scope. See Style a single component from a .css file.

Tailwind or UnoCSS regenerates but nothing swaps

Section titled “Tailwind or UnoCSS regenerates but nothing swaps”

Symptom. The generated file changes on disk, and the page does not.

Cause. The swap rides the HMR update for the .css module. The integration that writes the file has to emit one.

Fix. Check that the integration triggers HMR for that module. See Share one stylesheet with ?css-sheet.

CSSStyleSheet is not defined in jsdom or Node

Section titled “CSSStyleSheet is not defined in jsdom or Node”

Symptom. A test or an SSR render throws on import.

Cause. A sheet module constructs a CSSStyleSheet at import time, and neither jsdom nor Node provides one.

Fix. Do not import sheet modules in those environments. See CSS delivery.

Symptom. Vite DevTools opens with no Lit dock in it.

Cause. timeline defaults to false, DevTools() is missing from plugins, or @vitejs/devtools is not installed. When timeline is on and DevTools() is missing, the dev server prints a one-time [lit-plugin] timeline is on, but Vite DevTools never mounted the Lit panel. warning.

Fix. Add DevTools() to plugins (under Vite+, a devtools: key in the config does the same; use one, not both), or work through Set up the DevTools panel.

Symptom. The dev server refuses to start, naming a duplicate registration.

Cause. A devtools: key in the config already registers the overlay, and DevTools() in plugins registers it a second time.

Fix. Keep one of the two. See Set up the DevTools panel.

The Components tab says the page runtime has not connected

Section titled “The Components tab says the page runtime has not connected”

Symptom. The tab shows “The page runtime has not connected to this dev server.”

Cause. timeline is off, so no runtime script was injected; the page is not served through this dev server; or the script failed to load.

Fix. Set timeline: true or LIT_PLUGIN_TIMELINE=true, reload the page through the dev server, and check the browser console for a failed script.

Symptom. The tab shows “No Lit components found on the page.”

Cause. Nothing Lit has rendered yet, the components come from a pre-bundled dependency outside Vite’s transform, or lit loaded after the runtime announced itself.

Fix. Wait or navigate (with Live on, the tree picks up components as they render; if you paused it, turn it back on), and check Settings, About, for the Lit packages (lit-html, lit-element, @lit/reactive-element) the runtime detected.

The Components tab warns about more than one copy of lit

Section titled “The Components tab warns about more than one copy of lit”

Symptom. The tab says more than one copy of lit is loaded and names the duplicated package with its versions.

Cause. Two copies of lit, lit-element or @lit/reactive-element resolve, typically a dependency with its own nested lit. Components registered against another copy are invisible to the inspector and to HMR patching.

Fix. Set resolve.dedupe: ['lit', 'lit-element', '@lit/reactive-element'] in the Vite config, then reinstall and restart the dev server.

The Components tab says it is running inside an iframe

Section titled “The Components tab says it is running inside an iframe”

Symptom. The tab says the runtime is running inside an iframe.

Cause. The runtime loaded in a frame, so only that frame’s components are listed.

Fix. Open the framed app directly in its own tab.

The tree shows a different page, or the recording vanished

Section titled “The tree shows a different page, or the recording vanished”

Symptom. The Components tree changes to another page’s, or a recording you were reading is gone. A banner reads “Another page connected”.

Cause. The panel follows one page at a time. Opening the app in a second tab, an iframe reload or a Cmd-click on a link starts a new page, and the panel switches to it and clears the recording. Traffic from the page it left is ignored. Reloading the tab you are inspecting clears the recording too, but shows no banner.

Fix. Close the other tab, or reload the one you want to inspect, to switch back. Dismiss the banner with the close button.

Symptom. You interact with the page and no rows arrive.

Cause. Recording is off, or the layer you expect is toggled off. Layer toggles are remembered in localStorage across sessions.

Fix. Press ▶ Record, then check the layer pills. See Record and read the timeline.

Symptom. The row is there, and the Reasons column is empty.

Cause. The component overrides willUpdate or updated without calling super, so that phase is never reported.

Fix. Call super in the override. See Find out why a component re-rendered.

Symptom. The component table shows a ⚠ count, and an update row reads threw in update (or willUpdate, updated), rejected in updated, or task … failed.

Cause. That lifecycle phase threw, an async phase’s promise rejected unhandled, or a @lit/task failed. The component usually renders blank or stays stale, and the error also appears in the browser console. The panel records the error’s name and message only, never a stack.

Fix. Open the browser console for the stack, fix the throwing method, and record again. See Find out why a component re-rendered.

No Pick button, or Meta+Shift+E does nothing

Section titled “No Pick button, or Meta+Shift+E does nothing”

Symptom. The Components tab has no ⌖ Pick button, and the shortcut shows no picker on the page.

Cause. Under Vite, picking reuses the source-overlay picker, which is off by default. (A page connected to lit-devtools dev brings its own picker.)

Fix. Set sourceOverlay: true. See Open a component in your editor from the page.

Section titled “A source link opens nothing, or the wrong editor”

Symptom. Clicking a file path does nothing, or lands in another editor.

Cause. The shared open service refuses paths outside the workspace root and the panel falls back to the plugin’s own endpoint. The editor comes from launch-editor there, not from sourceOverlay.editor.

Fix. See DevTools architecture.

Settings I changed are missing in another browser

Section titled “Settings I changed are missing in another browser”

Symptom. Your overrides are gone in a second browser on the same machine.

Cause. The page reads a localStorage cache at boot, before any connection to DevTools exists.

Fix. Open the panel once in that browser; it refills the cache from the durable copy. See DevTools architecture.

The exported snapshot lacks details or early events

Section titled “The exported snapshot lacks details or early events”

Symptom. The frozen panel shows a tree with empty detail panes, or starts mid-session.

Cause. Only components you opened have details baked in, and the timeline keeps its most recent events.

Fix. Click the components that matter before exporting. lit-devtools build cannot export for you. See Record and read the timeline.

Symptom. No outline appears when you press the shortcut.

Cause. sourceOverlay is off by default, sourceOverlay.key was changed, or the browser claimed the combination first.

Fix. Enable it and pick a free key. See Open a component in your editor from the page.

Symptom. Clicking an element opens nothing in the hosted preview.

Cause. Opening goes through a dev-server middleware, and falls back to the editor’s URL scheme when the server is unreachable.

Fix. Nothing. This is expected in a hosted preview. See Open a component in your editor from the page.

Symptom.

Terminal window
[lit-devtools] this command needs the optional peer "cac". Install it and retry:
npm install -D cac

Cause. cac and @devframes/agentic are optional peers, imported only on the path that needs them.

Fix. Install the named package. See CLI.

Symptom. devframe_connect_list-instances comes back empty.

Cause. No dev server is running, or registry discovery was unavailable and the plugin warned about it at startup.

Fix. Start the server, or bridge the URL directly with mcp-remote http://localhost:<port>/__devtools/__mcp. See Connect a coding agent.

Symptom. The tool answers with recording: false and no events.

Cause. Recording is off, and nothing is being captured.

Fix. Call lit_set-recording, or press ▶ Record in the panel. See Connect a coding agent.

Symptom. LIT_PLUGIN_* has no effect.

Cause. An explicit litPlugin() option wins over the variable. Booleans also only accept true, 1, false and 0.

Fix. Remove the explicit option, fix the value, and restart the dev server after editing .env. See Environment variables.

Symptom. A startup warning names the variable and lists the allowed values.

Cause. The value is outside the closed set, usually a typo.

Fix. Use auto, url, inline or inline-raw. A typo falls back to the default rather than disabling the feature. See Environment variables.

Rolldown full-bundle dev mode gives plain reloads

Section titled “Rolldown full-bundle dev mode gives plain reloads”

Symptom. No HMR features apply at all.

Cause. The plugin targets the standard Vite dev server pipeline, where each module is served and re-executed on its own.

Fix. Use the standard dev server. There is no supported configuration of the full-bundle mode. See Limitations.

virtual:lit-plugin/timeline breaks vite build or reports TS2307

Section titled “virtual:lit-plugin/timeline breaks vite build or reports TS2307”

Symptom. The build fails, or TypeScript cannot resolve the virtual module.

Cause. Both were bugs before 0.3.0, which moved the hooks and shipped ambient types.

Fix. Upgrade to 0.3.0 or later. See Changelog.