Skip to content

Options

litPlugin() takes a single options object, and this page lists every key in it. Each entry gives the meaning, a config you can paste, the default, the environment variable, and where to read more.

Option Type Default Env var
hmr boolean | HmrOptions true LIT_PLUGIN_HMR
hmr.enabled boolean true LIT_PLUGIN_HMR
hmr.reconnect boolean false LIT_PLUGIN_HMR_RECONNECT
hmr.privateFields boolean true LIT_PLUGIN_HMR_PRIVATE_FIELDS
hmr.childState 'reset' | 'transfer' | 'reuse' 'transfer' LIT_PLUGIN_HMR_CHILD_STATE
hmr.onIncompatible 'reload' | 'warn' 'reload' LIT_PLUGIN_HMR_ON_INCOMPATIBLE
hmr.indicator boolean | HmrIndicatorOptions true LIT_PLUGIN_HMR_INDICATOR
hmr.indicator.count boolean false LIT_PLUGIN_HMR_INDICATOR_COUNT
sourceOverlay boolean | SourceOverlayOptions false LIT_PLUGIN_SOURCE_OVERLAY
sourceOverlay.key string 's' LIT_PLUGIN_SOURCE_OVERLAY_KEY
sourceOverlay.editor string | EditorConfig 'vscode' LIT_PLUGIN_SOURCE_OVERLAY_EDITOR
sourceOverlay.workspaceRoot string — —
sourceOverlay.throttleMs number 50 LIT_PLUGIN_SOURCE_OVERLAY_THROTTLE_MS
sourceOverlay.hosts 'source' | 'lit' 'source' —
sourceOverlay.exclude (el: Element) => boolean — —
sourceOverlay.onSelect (info: ElementInfo) => void — —
timeline boolean false LIT_PLUGIN_TIMELINE
cssSheetBuild 'auto' | 'url' | 'inline' | 'inline-raw' 'auto' LIT_PLUGIN_CSS_SHEET_BUILD
devtoolsWorkspace boolean | string true LIT_PLUGIN_DEVTOOLS_WORKSPACE

Turns HMR for Lit component classes, and its on-page indicator, on or off.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({hmr: false})],
});

false skips every HMR transform and all runtime injection. The CSS import queries and the Lightning CSS literal processing still apply.

Type: boolean | HmrOptions · Default: true · Env: LIT_PLUGIN_HMR (maps to hmr.enabled)

See also: HMR that keeps state.

The same switch as hmr: false, spelled out so you can keep configuring the rest of the group.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({hmr: {enabled: false, indicator: true}})],
});

Type: boolean · Default: true · Env: LIT_PLUGIN_HMR

See also: HMR that keeps state.

Cycles disconnectedCallback() and connectedCallback() on live instances after a hot patch, an edit applied to the running page without a reload.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({hmr: {reconnect: true}})],
});

Cycling tears down and re-creates controllers, listeners, and animations, which a plain patch leaves alone. Turn it on when a component does setup work in connectedCallback() that you want re-run on every edit.

Type: boolean · Default: false · Env: LIT_PLUGIN_HMR_RECONNECT

See also: HMR that keeps state.

Rewrites native #private class members to keys the next evaluation of the module shares, so components that use them are patched in place and keep their private state. Only the dev server does this; builds keep real #private.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({hmr: {privateFields: false}})],
});

In dev, a private member becomes a symbol-keyed property. It no longer throws when read from the wrong object, and Object.getOwnPropertySymbols() lists it. Turn the rewrite off if a test or a library relies on the brand check in dev.

Type: boolean · Default: true · Env: LIT_PLUGIN_HMR_PRIVATE_FIELDS

See also: Limitations.

What happens to a child element when a parent edit changes the template literal it sits in. Lit clones fresh DOM for an edited template, so the child is a new element.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({hmr: {childState: 'reuse'}})],
});
  • 'transfer' copies the old element’s reactive properties and #private fields onto the new one before its first render. Properties and attributes the template sets are left alone, so an edited binding takes effect.
  • 'reuse' puts the old element back in place of the new one, keeping its identity, shadow DOM and controllers. It only does so when no binding targets the new element or its light DOM; otherwise it transfers.
  • 'reset' leaves the new element at its defaults.

Old and new elements are matched by tag and order within the same shadow root. An edit that removes one child and adds another of the same tag in the same root can hand the removed child’s state to the new one.

Type: 'reset' | 'transfer' | 'reuse' · Default: 'transfer' · Env: LIT_PLUGIN_HMR_CHILD_STATE

See also: HMR that keeps state.

Chooses what happens when a component cannot be patched in place: reload the page, or only warn in the console.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({hmr: {onIncompatible: 'warn'}})],
});

'reload' loses page state but never shows a half-patched component. 'warn' logs one line and leaves the page alone, so you can read the message.

Type: 'reload' | 'warn' · Default: 'reload' · Env: LIT_PLUGIN_HMR_ON_INCOMPATIBLE

See also: Limitations.

Injects the on-page update indicator, a small dot that animates on each HMR update.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({hmr: {indicator: false}})],
});

The indicator is forced off when HMR itself is disabled, whatever this is set to. It pulses in one of two colours, and only some updates advance its count:

Playground edit Delivery Re-renders? Counts? Pulse
hmr-vsheet.css ?css-sheet shared sheet no no info (cyan)
hmr-shared.css ?raw → shared sheet no no info (cyan)
hmr-utility-sheet.css ?url → shared sheet no no info (cyan)
hmr-linked-css.css ?hmr-url <link> yes yes success (green)
hmr-import-css.css ?hmr-url @import yes yes success (green)
hmr-css-url.css ?url + devCacheBust() yes yes success (green)
hmr-raw-css.css ?raw static styles yes yes success (green)
any *.ts component component module yes yes success (green)
main.ts not self-accepting full reload resets —

A shared-stylesheet hot-swap restyles adopted sheets in place without re-rendering, so it pulses in the calmer info colour. A full reload remounts everything and resets the count.

Type: boolean | HmrIndicatorOptions · Default: true · Env: LIT_PLUGIN_HMR_INDICATOR (maps to hmr.indicator.enabled)

See also: Build your first component.

Shows a cumulative update count next to the dot.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({hmr: {indicator: {count: true}}})],
});

The count raises the idle opacity from fully transparent to 0.5, so the number stays readable between updates.

Type: boolean · Default: false · Env: LIT_PLUGIN_HMR_INDICATOR_COUNT

See also: Read the update explainer.

Enables the dev-only inspector that opens a clicked element’s source file in your editor.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({sourceOverlay: true})],
});

Toggle it in the page with Ctrl+Shift+S, or ⌘⇧S on a Mac. true takes the defaults below; an object configures them.

Type: boolean | SourceOverlayOptions · Default: false · Env: LIT_PLUGIN_SOURCE_OVERLAY

See also: Open a component in your editor.

Sets the letter that Ctrl+Shift is combined with to toggle the overlay.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({sourceOverlay: {key: 'i'}})],
});

Type: string · Default: 's' · Env: LIT_PLUGIN_SOURCE_OVERLAY_KEY

See also: Open a component in your editor.

Picks which editor a click opens.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({sourceOverlay: {editor: 'zed'}})],
});

Built-in keys are vscode, cursor, zed, idea, and windsurf. An unknown name falls back to VS Code. A {name, url} object supplies your own URL builder, called with the file path, the line number and, for a call site, the column.

The DevTools panel’s source links also open in this editor (vscode, cursor, zed and idea). Other values leave the choice to launch-editor’s auto-detection. See Open a component in your editor.

Type: string | EditorConfig · Default: 'vscode' · Env: LIT_PLUGIN_SOURCE_OVERLAY_EDITOR

See also: Open a component in your editor.

Prefixes the absolute file paths the overlay hands to your editor.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({sourceOverlay: {workspaceRoot: '/Users/me/app'}})],
});

Type: string · Default: none · Env: none

See also: Open a component in your editor.

Throttles how often the overlay recomputes the element under the pointer.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({sourceOverlay: {throttleMs: 16}})],
});

Type: number · Default: 50 · Env: LIT_PLUGIN_SOURCE_OVERLAY_THROTTLE_MS

See also: Open a component in your editor.

Which elements the picker can pick. 'source' is your own components, the ones with a file to open. 'lit' adds every other Lit element on the page, such as a component library’s <wa-button> or <sl-input>: those pick into the DevTools Components tab, with no source to open. Your own components keep theirs.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({sourceOverlay: {hosts: 'lit'}})],
});

Type: 'source' | 'lit' · Default: 'source' · Env: none

See also: Open a component in your editor.

Skips elements during inspection. Return true to ignore an element.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [
litPlugin({
sourceOverlay: {exclude: (el) => el.tagName.startsWith('SL-')},
}),
],
});

Type: (el: Element) => boolean · Default: none · Env: none, this is a function

See also: Open a component in your editor.

Runs when you click a component, instead of or alongside opening the editor.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [
litPlugin({
sourceOverlay: {onSelect: (info) => console.log(info.source.filePath)},
}),
],
});

The callback receives {tagName, componentName?, source: {filePath, lineNumber}}.

Type: (info: ElementInfo) => void · Default: none · Env: none, this is a function

See also: Open a component in your editor.

Adds the Lit panel to Vite DevTools, the browser overlay from @vitejs/devtools that hosts plugin panels.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({timeline: true})],
});

The panel records Lit lifecycle, render, mouse, keyboard, and custom events and Lit warnings, and derives per-component update counts, durations, and reasons from them. It needs @vitejs/devtools active in the same Vite config. true enables every built-in layer, one category of recorded events with its own colour and toggle.

Type: boolean · Default: false · Env: LIT_PLUGIN_TIMELINE

See also: Open the DevTools panel.

Decides what a ?css-sheet import compiles to under vite build.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({cssSheetBuild: 'inline'})],
});

Dev always uses the fetch-backed HMR form, whatever this is set to.

Value Build output
'auto' 'inline' when build.lib is set, 'url' otherwise
'url' fetch-backed sheet over the emitted .css asset
'inline' CSS text in the JS chunk, processed by the CSS pipeline
'inline-raw' CSS text in the JS chunk verbatim, skipping the CSS pipeline

A value outside that set logs a warning and falls back to the default, so a typo cannot change your build output silently.

Type: 'auto' | 'url' | 'inline' | 'inline-raw' · Default: 'auto' · Env: LIT_PLUGIN_CSS_SHEET_BUILD

See also: Share one stylesheet with ?css-sheet.

Lets Chrome DevTools connect your project as a workspace, so edits you make in the Sources panel save to disk. In dev, the server answers /.well-known/appspecific/com.chrome.devtools.json with the folder and a stable ID, and DevTools offers to connect it.

vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';
export default defineConfig({
plugins: [litPlugin({devtoolsWorkspace: '../..'})],
});

true uses Vite’s workspace root, which in a monorepo is the repository root rather than the app, so sources served from sibling packages map too. A string names the folder, relative to Vite’s root. false leaves the request unanswered.

The reply contains an absolute path on your machine, so the server only gives it to clients on the same machine. A dev server exposed with --host passes requests from the network on to Vite.

Type: boolean | string · Default: true · Env: LIT_PLUGIN_DEVTOOLS_WORKSPACE