# Options

> Every option litPlugin() and the Astro integration accept, with its type, default, environment variable, and the page that explains it.

`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.

Two places, one surface

The entries here mirror the JSDoc in `src/lib/plugin.ts`, and the two change in the same commit. Environment variables are documented on [their own page](/reference/environment-variables/).

| 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`         |

## hmr

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

```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](/guides/hmr/).

### hmr.enabled

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

```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](/guides/hmr/).

### hmr.reconnect

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

```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](/guides/hmr/).

### hmr.privateFields

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`.

```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](/reference/limitations/).

### hmr.childState

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.

```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](/guides/hmr/).

### hmr.onIncompatible

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

```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](/reference/limitations/).

### hmr.indicator

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

```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](/start/first-component/).

### hmr.indicator.count

Shows a cumulative update count next to the dot.

```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](/guides/devtools/updates/).

## sourceOverlay

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

```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](/guides/source-overlay/).

### sourceOverlay.key

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

```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](/guides/source-overlay/).

### sourceOverlay.editor

Picks which editor a click opens.

```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](/guides/source-overlay/#choose-your-editor).

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

See also: [Open a component in your editor](/guides/source-overlay/).

### sourceOverlay.workspaceRoot

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

```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](/guides/source-overlay/).

### sourceOverlay.throttleMs

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

```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](/guides/source-overlay/).

### sourceOverlay.hosts

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.

```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](/guides/source-overlay/).

### sourceOverlay.exclude

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

```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](/guides/source-overlay/).

### sourceOverlay.onSelect

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

```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](/guides/source-overlay/).

## timeline

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

```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. With `@vitejs/devtools` active in the same Vite config the panel docks there; without it, the dev server serves the panel itself at `/__lit/`. `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](/guides/devtools/).

## cssSheetBuild

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

```ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';

export default defineConfig({
  plugins: [litPlugin({cssSheetBuild: 'inline'})],
});
```

Dev always builds the sheet from the `?inline` text and swaps it on each edit, 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`](/guides/stylesheets/css-sheet/).

## devtoolsWorkspace

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.

```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`

## Astro options

`lit()` from `@oddsquad/vite-plugin-lit/astro` takes every option above, with `timeline` on by default, since the panel in the dev toolbar needs it (`LIT_PLUGIN_TIMELINE=false` still turns it off), and three of its own. They mirror the JSDoc of `LitAstroOptions` in `src/astro.ts` and have no environment variables. `litDevtools()` takes the same options except `render` and `shadowRootPolyfill`.

| Option               | Type      | Default |
| -------------------- | --------- | ------- |
| `render`             | `boolean` | `true`  |
| `shadowRootPolyfill` | `boolean` | `false` |
| `toolbar`            | `boolean` | `true`  |

### render

Registers the integration as Astro’s renderer for Lit, which renders Lit components on the server and hydrates them. It needs `@lit-labs/ssr` and `@lit-labs/ssr-client`; if one is missing, the dev server and the build stop and say what to install.

```js
import {defineConfig} from 'astro/config';
import lit from '@oddsquad/vite-plugin-lit/astro';

export default defineConfig({
  integrations: [lit({render: false})],
});
```

`false` keeps the plugin’s HMR and the panel and leaves rendering to another Lit renderer, such as `@awesome.me/astro-lit`. `litDevtools()` is `lit()` with `render: false`.

**Type:** `boolean` · **Default:** `true`

### shadowRootPolyfill

Attaches declarative shadow roots in browsers that can’t parse them, before any of your components is defined, so the server’s HTML still hydrates rather than rendering again. Every current browser parses them natively, and the polyfill does nothing there. It is part of the plugin: there is nothing to install.

```js
import {defineConfig} from 'astro/config';
import lit from '@oddsquad/vite-plugin-lit/astro';

export default defineConfig({
  integrations: [lit({shadowRootPolyfill: true})],
});
```

**Type:** `boolean` · **Default:** `false`

### toolbar

Puts the Lit panel in Astro’s dev toolbar, in place of the Vite DevTools dock. The panel needs the timeline, so with `timeline: false` there is no toolbar app either.

```js
import {defineConfig} from 'astro/config';
import lit from '@oddsquad/vite-plugin-lit/astro';

export default defineConfig({
  integrations: [lit({toolbar: false})],
});
```

`false` leaves the panel at `/__lit/` on the dev server, the address `astro dev` prints at startup.

**Type:** `boolean` · **Default:** `true`

## Related

[Environment variables](/reference/environment-variables/)The same surface, resolved from the environment.

[Limitations](/reference/limitations/)What onIncompatible reacts to.

[Use Lit in Astro](/guides/astro/)Where lit() and its options fit.
