Language server
The Lit language server reads your components from source, as you type, and
your dependencies’ Custom Elements Manifests, and answers in html and svg
templates, in HTML files, in JSX, Astro and MDX, and in css templates.
Use the language server sets it up.
It reads the same JSDoc the manifest does, so this component is the example below:
/** * A button that submits the form it sits in. * * @fires submit-start - Before the form is sent. * @cssprop [--x-button-gap=4px] - Space between icon and label. * @csspart base - The native button. */@customElement('x-button')export class XButton extends LitElement { /** How much room the button takes. */ @property({reflect: true}) size: 'small' | 'large' = 'small';
/** Arbitrary data, never an attribute. */ @property({attribute: false}) data: Record<string, unknown> = {};
#submit() { this.dispatchEvent( new CustomEvent('submit-start', {detail: {size: this.size, at: 0}}) ); }}Completion
Section titled “Completion”In an html template, ‸ marking the cursor:
| You type | It offers |
|---|---|
<x-‸ |
Your components, the tags of your dependencies’ manifests, then HTML and SVG elements |
<x-button ‸ |
Its attributes not yet written, then the global ones (id, class, slot…) |
<x-button .‸ |
Its properties: size, data |
<x-button ?‸ |
Its boolean attributes, then the global boolean ones (hidden, inert…) |
<x-button @‸ |
Its events, then the events your other components fire, then DOM events (click, input…) |
<x-button size="‸" |
The values of a union of string literals: small, large |
<path stroke-linejoin="‸" |
Fixed values of HTML and SVG attributes: miter, round, bevel… |
Events bubble, so @ on any element offers the custom events any of your
components fires. A parent can listen for its children’s events:
html`<form @submit-start=${this.#onStart}>…</form>`;// ^ offered: <x-button> fires itIn HTML files the server completes your components and their attributes, and leaves standard elements to the editor’s own HTML support.
| Over | It shows |
|---|---|
| A component’s tag | Its summary, description, attributes, properties, events, slots, parts and CSS properties |
An attribute or .property |
Its type, default, the attribute or property it maps to, where it’s inherited from |
An @event |
Who fires it, and what a listener receives: Event CustomEvent · detail {size: 'small' | 'large'; at: number} |
| An HTML or SVG element or attribute | Its description and a link to MDN |
A property in a css template |
Its description and browser support |
An event’s type comes from the dispatch when no @fires {Type} names one:
CustomEvent<T>’s type argument, else the shape of the detail object,
each key typed by the field it reads or the literal it holds. What can’t be
told without a type checker is unknown.
JSX, Astro and MDX
Section titled “JSX, Astro and MDX”The server reads your components in JSX in .jsx and .tsx files, in an
Astro component’s template, and in MDX. Completion, hover, go to
implementation and find references work there as in a Lit template, with
each format’s way of writing a binding:
| You write | In JSX (.jsx, .tsx, MDX) |
In Astro |
|---|---|---|
size="small" |
The size property, else the size attribute |
The same |
selectedItem={item} |
A property by its name | The same |
onsubmit-start={fn} |
The submit-start event, as React 19 and Preact read it |
An attribute |
on:submit-start={fn} |
The event, Solid’s way; so are prop:, attr:, bool: |
An attribute |
Only your components get completed in these files: standard HTML elements
are left to the editor’s own support for the language. In Astro, the
templates in the frontmatter and in <script> elements are Lit templates.
In MDX, fenced and inline code and import/export blocks are skipped.
The typed event handler features and the event-type check read Lit’s
@event=${…} bindings only.
Go to implementation and find references
Section titled “Go to implementation and find references”| From | Go to implementation (and definition) goes to |
|---|---|
| A component’s tag | Its class |
An attribute or .property |
The field, accessor or static properties entry, in the class or the base class declaring it |
| An attribute with no property | Its entry in observedAttributes |
An @event |
The event name in the new CustomEvent('…') the class dispatches, else the class |
Find references, from the same places, lists every use in the workspace:
opening and closing tags, and an attribute or property under either of its
names (size and .size are one member). It searches every script it reads
components from, the HTML files at each package’s root, the HTML, Astro and
MDX files in the folders it reads, and any open file.
A component from a dependency’s manifest has no source to go to; its uses are still found.
Event handlers
Section titled “Event handlers”In a template TypeScript gives an inline handler’s parameter no type, since nothing tells it which event the binding is for. The server knows, and helps until the type is written:
html`<x-button @submit-start=${(ev) => ev.detail.‸}></x-button>`;// ^^ inlay hint: CustomEvent<{size: 'small' | 'large'; at: number}>// completion after ev.detail.: size, at- Inlay hint. The event’s type after the untyped parameter.
- Quick fix, “Add event type”. Writes it in, after which TypeScript
completes and checks the handler itself:
html`<x-button@submit-start=${(ev: CustomEvent<{size: 'small' | 'large'; at: number}>) => go(ev)}></x-button>`;
- Completion. After
ev.:detailand what every event has (target,preventDefault…); afterev.detail.: the keys of its shape.
Problems it reports
Section titled “Problems it reports”A handler that doesn’t fit its event
Section titled “A handler that doesn’t fit its event”A handler whose parameter expects something the event doesn’t send is an
error, event-type. It checks method references and typed inline handlers:
class App extends LitElement { render() { return html`<x-button @submit-start=${this.#onStart}></x-button>`; // ^^^^^^^^^^^^^ `#onStart` takes `CustomEvent<{thing: string}>`, // but `submit-start` sends `CustomEvent<{size: 'small' | 'large'; at: number}>`: `detail.thing` is missing. }
#onStart(e: CustomEvent<{thing: string}>) {}}It reports a detail key the handler expects that the event doesn’t send,
and one primitive type where another is sent (string for number), nested
keys included. Fewer keys than sent, optional keys, named types such as
CustomEvent<Item>, unions and other event classes are never reported: a
reported conflict is a real one.
CSS in css templates
Section titled “CSS in css templates”Unknown properties and invalid values in a css template are warnings, css.
Lines with a ${…} aren’t checked, since the CSS around a hole is often
incomplete on purpose.
static styles = css` :host { colo: red; } /* ^^^^ Unknown property: 'colo' */ .gap { margin: ${unsafeCSS(gap)}; } /* not checked */`;Settings
Section titled “Settings”A mistake in the settings, such as an unknown key or severity, is a warning on
the first line of the file that holds it. So is a lit-devtools.config.ts that
a Vite config overrides.
CSS templates
Section titled “CSS templates”In a css template the server answers with VS Code’s CSS language service:
properties, values, selectors and at-rules complete, and hover shows their
docs. It adds what only your components’ docs know:
static styles = css` .row { gap: var(--x‸); } /* offered: --x-button-gap = 4px, from <x-button> */ x-button::part(‸) {} /* offered: base */`;Settings
Section titled “Settings”Set them in litPlugin({languageServer}) in a Vite config, or as
languageServer in a lit-devtools.config.{ts,mts,js,mjs,json} in a project
without one. When both exist, the Vite config’s settings win as a whole and
the config file is reported as ignored.
| Setting | Default | Meaning |
|---|---|---|
scan |
the manifest option’s scan, else ['src'] |
Folders to read components from, relative to the package |
manifests |
[] |
More manifests to read: paths relative to the package, or URLs |
ignoreTags |
[] |
Tags no problem is reported for, with * wildcards |
ignoreAttributes |
[] |
Attributes accepted on any element, with * wildcards |
diagnostics |
see below | Each rule’s severity: error, warning, info, hint or off |
templateTags |
['html', 'svg'] |
Tags of the markup templates to read |
cssTemplateTags |
['css'] |
Tags of the CSS templates to read |
| Rule | Default | Reports |
|---|---|---|
event-type |
error |
A handler whose parameter doesn’t fit its event |
css |
warning |
A problem in a css template |
ignoreTags, ignoreAttributes, manifests, entries and the rules
unknown-element, unknown-attribute, unknown-binding, invalid-value
and missing-import are read and validated but not acted on yet.
Oxlint plugin
Section titled “Oxlint plugin”@oddsquad/lit-devtools/oxlint (or @oddsquad/oxlint-plugin-lit on its own)
reports the same problems in a lint run, one
rule per problem kind, named lit-devtools/<rule> so they don’t clash with
eslint-plugin-lit’s lit/ rules:
| Rule | Reports |
|---|---|
lit-devtools/event-type |
A handler whose parameter doesn’t fit its event |
lit-devtools/css |
A problem in a css template |
Its default export has configs.recommended, a config for extends in
oxlint.config.ts or in Vite+’s lint: the plugin, with each rule at its
default severity from the table above (warning becomes warn). Without
it, the rules are off until turned on in Oxlint’s rules. Either way
Oxlint’s config sets their severity; the diagnostics setting applies to
the editor only. The other settings are read as above. // oxlint-disable
comments silence them. The plugin also registers
lit-devtools/unknown-element, lit-devtools/unknown-attribute,
lit-devtools/unknown-binding, lit-devtools/invalid-value and
lit-devtools/missing-import, which report nothing yet; recommended
already turns on the ones the editor does. It reads JavaScript and
TypeScript files only.
Check it in CI with Oxlint
sets it up.
Where its knowledge comes from
Section titled “Where its knowledge comes from”| Source | For |
|---|---|
| Your sources, read live from open buffers | Your components, in every package of the workspace |
| Your dependencies’ Custom Elements Manifests | Components from libraries, in the packages that depend on them |
| VS Code’s HTML data | HTML elements, attributes, global attributes and DOM events |
svg-element-attributes, plus short descriptions |
SVG elements and attributes |
| TypeScript’s DOM event maps | The interface each DOM event dispatches (click: MouseEvent) |
| VS Code’s CSS language service | CSS in css templates |