Manifest generators compared
Pick the tool that writes your custom-elements.json, by what each one reads
from your components and where its paths point. Skip this page if you only
want to turn the plugin’s manifest option on.
Every cell below comes from running the four tools on one small Lit library in
October 2026: an element that extends a mixin over a base class, with
decorators, JSDoc, a constructor, dispatched events, a vanilla element and a
barrel file. The versions were Lit DevTools 0.24,
@custom-elements-manifest/analyzer 0.11.0 with --litelement,
cem 0.11.4 and @lit-labs/gen-manifest 0.3.6 through
lit labs gen --manifest. Follow the links for their current state.
At a glance
Section titled “At a glance”| Lit DevTools | CEM analyzer | cem | Lit gen-manifest | |
|---|---|---|---|---|
| Runs as | Vite plugin option, lit-devtools manifest |
CLI, or create() in the browser |
CLI (a Go binary on npm) | lit labs gen --manifest |
| Schema version written | 2.1.0 | 1.0.0 | 2.1.0 | 1.0.0 |
| Module paths | The built files under vite build, else source |
Source (src/x.ts) |
Source, renamed to .js |
The tsconfig outDir (out/x.js) |
@property() attribute and reflects |
Yes | Yes | Yes, but lost on a class that extends a mixin | Yes |
| Types | As written; the type: option when unannotated |
As written | As written | From the type checker |
@state() and private members |
Left out | Listed, with privacy |
Private listed, @state() left out |
Listed, with privacy |
| Methods, with parameters and return type | Yes | Yes; the return type came out empty | Yes, without the @returns description |
Yes |
render() and @internal methods |
Left out | Left out | Listed | Listed |
Events from @fires |
Yes | Yes | Yes | Yes |
Events from this.dispatchEvent(…) |
Yes, literal names only | Yes, and one named after a variable | No | No |
| Defaults set in the constructor | Yes | Yes | No | No |
| Slots, parts, CSS custom properties | Yes | Yes | Yes | Yes |
CSS states (@cssstate) |
Yes | Yes | Yes | No |
@attr, @prop, @default, @tag tags |
Yes | Yes | @attr and @default |
No |
Inherited members, marked inheritedFrom |
Yes, across modules, mixins and dependencies’ manifests | Yes | No | No |
| Mixin declarations | Yes | Yes | No | Yes |
A vanilla HTMLElement element |
Yes | Yes | No | No |
observedAttributes |
Yes | Yes | No | No |
Barrel files (export * from) |
Resolved to each name, through chains of barrels | Listed as a * export |
Resolved to each name | Resolved to each name |
| Watch mode | The dev server, through HMR and a file watcher | --watch |
--watch |
No option |
| Also does | The same docs in the DevTools panel and Lit Inspector | A plugin API; FAST, Stencil and Catalyst | A language server, MCP, a dev server, validate |
React and Vue wrappers |
CEM analyzer
Section titled “CEM analyzer”The analyzer is the reference most tools read against, and it reads the
widest syntax, frameworks beyond Lit included, through plugins. It lists private members and internal
state with their privacy, so a reader that wants the public API filters them
out. Its paths name your source files, so a published package needs a plugin
to point them at the built ones.
cem writes schema 2.1 and comes with tooling built on the manifest: a
language server, an MCP server, a dev server and a validator. On our library
it read decorated properties well on an element that extends LitElement,
but not on one that extends a mixin call. It doesn’t merge inherited members,
infer dispatched events or read constructor defaults.
Lit’s gen-manifest
Section titled “Lit’s gen-manifest”The Lit Labs generator runs the TypeScript checker, so it reports types you
never wrote, such as string for a parameter that defaults to 'info'. Its
paths follow your tsconfig’s outDir. It lists lifecycle and @internal
methods and doesn’t merge inherited members. It is experimental, like the rest
of Lit Labs.
Lit DevTools
Section titled “Lit DevTools”The plugin reads your components the way its DevTools panel does, so the
manifest, the panel and Lit Inspector show the same docs. Under vite build
its paths name the files you ship, and tree-shaken components are left out.
In dev the manifest follows every edit. Its types are the text you wrote,
and the table’s “No” rows are what it doesn’t read yet.