Troubleshooting
Find the symptom you are seeing, read the cause, apply the fix. Every entry links to the page that explains the behaviour.
Edits reload the page
Section titled “Edits reload the page”Every save reloads the page
Section titled “Every save reloads the page”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.
[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.
The console says “patching failed”
Section titled “The console says “patching failed””Symptom.
[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.
[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.
[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.
Patched, but state still resets or breaks
Section titled “Patched, but state still resets or breaks”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.
Editing a @lit/task body does nothing
Section titled “Editing a @lit/task body does nothing”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.
Uncaught TypeError: Cannot read private member #x from an object whose class did not declare itCause. 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.
reconnect: true seems to do nothing
Section titled “reconnect: true seems to do nothing”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.
The indicator
Section titled “The indicator”There is no indicator
Section titled “There is no indicator”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.
The count went back to 0
Section titled “The count went back to 0”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.
Stylesheets
Section titled “Stylesheets”TS2307, or the import is typed any
Section titled “TS2307, or the import is typed any”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.
The shared sheet applies nothing
Section titled “The shared sheet applies nothing”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.
Library consumers get no styles
Section titled “Library consumers get no styles”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.
Lightning CSS errors on @property
Section titled “Lightning CSS errors on @property”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.
urlSheet() edits do not swap
Section titled “urlSheet() edits do not swap”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().
A link keeps stale CSS with ?url
Section titled “A link keeps stale CSS with ?url”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.
The DevTools panel
Section titled “The DevTools panel”The panel does not appear
Section titled “The panel does not appear”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.
Vite errors with DTK0034
Section titled “Vite errors with DTK0034”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.
The Components tab is empty
Section titled “The Components tab is empty”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.
The timeline is empty
Section titled “The timeline is empty”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.
Updates shows a tick with no changed keys
Section titled “Updates shows a tick with no changed keys”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.
A component shows ⚠ in Updates
Section titled “A component shows ⚠ in Updates”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.
A source link opens nothing, or the wrong editor
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.
The source overlay
Section titled “The source overlay”Ctrl+Shift+S (or ⌘⇧S) does nothing
Section titled “Ctrl+Shift+S (or ⌘⇧S) does nothing”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.
It works locally but not on StackBlitz
Section titled “It works locally but not on StackBlitz”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.
The CLI and agents
Section titled “The CLI and agents”The command needs an optional peer
Section titled “The command needs an optional peer”Symptom.
[lit-devtools] this command needs the optional peer "cac". Install it and retry:
npm install -D cacCause. cac and @devframes/agentic are optional peers, imported only on
the path that needs them.
Fix. Install the named package. See CLI.
The agent lists no instances
Section titled “The agent lists no instances”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.
lit_recent-events returns nothing
Section titled “lit_recent-events returns nothing”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.
Configuration
Section titled “Configuration”An environment variable is ignored
Section titled “An environment variable is ignored”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.
LIT_PLUGIN_CSS_SHEET_BUILD is rejected
Section titled “LIT_PLUGIN_CSS_SHEET_BUILD is rejected”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.