Publish a Custom Elements Manifest
Ship a custom-elements.json that documents your components, written from the
same source reading the DevTools panel uses. Skip this page if you already generate one with the analyzer and are happy
with it; Manifest generators compared sets the
tools side by side.
A Custom Elements Manifest is a JSON file that lists each element’s tag,
properties, attributes, events, slots, parts and CSS properties. Editors,
Storybook and documentation sites read it, and a package points at it with the
customElements field in its package.json.
Turn it on
Section titled “Turn it on”Set manifest: true. vite build then emits custom-elements.json beside the
bundle, and the dev server serves it at /custom-elements.json.
import {defineConfig} from 'vite';import {litPlugin} from '@oddsquad/lit-devtools/vite';
export default defineConfig({ plugins: [litPlugin({manifest: true})],});The build reads every module of the bundle, so its manifest covers each
component the bundle contains. Each module path names the built file that
holds the component, such as dist/my-lib.js, because that is what a consumer
imports. The dev server reads every script under src when it starts and follows
each change on disk, so its manifest lists your components before any page
loads them. Set scan to the folders your components live in, or to false
to list only what pages load. It links the manifest in each page’s head, so
the browser extension shows the docs too.
Write what the manifest reads
Section titled “Write what the manifest reads”Document a component with JSDoc on the class and its fields:
import {LitElement, html} from 'lit';import {customElement, property} from 'lit/decorators.js';
/** * A button that submits the form it sits in. * * @summary The form's submit button. * @fires submit-start - Before the form is sent. * @slot - The label. * @csspart base - The native button. * @cssprop [--x-button-gap=4px] - Space between icon and label. */@customElement('x-button')export class XButton extends LitElement { /** How much room the button takes. */ @property({reflect: true}) size: 'small' | 'large' = 'small';
render() { return html`<button part="base"><slot></slot></button>`; }}The class comment gives the description and each tagged entry. A field’s
comment gives its description, its type annotation the type, and its
initializer the default. @property() names the attribute it maps to, and
reflect: true marks it reflects. @state() and private members stay out.
The components guide lists
every tag the reader understands.
Public methods are listed too, with their parameters and return type. Types
come from the annotations, or from @param {Type} name - description and
@returns {Type} description in plain JavaScript. Lifecycle callbacks such as
render(), updated() and connectedCallback() are how Lit calls your class,
so they stay out.
A class lists the members it inherits from your own base classes and mixins,
each marked inheritedFrom, so tools need no inheritance walk. Types are the
text you wrote; nothing is inferred from the checker.
Keep a copy in your package
Section titled “Keep a copy in your package”Add write to keep the file in your project as well. The dev server rewrites
it as you edit, and vite build rewrites it from every module of the bundle.
import {defineConfig} from 'vite';import {litPlugin} from '@oddsquad/lit-devtools/vite';
export default defineConfig({ plugins: [litPlugin({manifest: {write: true}})],});When your package.json has no customElements field yet, the plugin sets it
to the file it wrote. A field you already set is left alone.
Write it from the command line
Section titled “Write it from the command line”lit-devtools manifest reads every script under src, not only the ones a
build reached, and writes custom-elements.json. Run it before you publish:
{ "scripts": { "prepublishOnly": "lit-devtools manifest" }}It resolves imports through your Vite config, so aliases work; without one,
through tsconfig.json paths and package exports. The
CLI reference lists its flags.