Skip to content

Documenting Web Components

This guide explains how to document Web Components using JSDoc comments. The analyzer reads your source files and extracts as much information as it can automatically. JSDoc tags let you fill in the gaps, override inferred values, and provide richer documentation where needed.


Component Overview

Tag Name and Title

The analyzer detects the custom element tag name from the class automatically. Two tags let you override the defaults:

  • @tagName | @name — Override the detected tag name.
  • @title — Override the page title shown in the documentation. If omitted, the tag name is used as the title.
/**
* @tagName my-component
* @title My Component
*/
class MyComponent extends HTMLElement {}

Description

The JSDoc description block is treated as Markdown. You can write headings, lists, inline code, and any other standard Markdown syntax.

/**
* A button component that supports icons and loading states.
*
* ## Usage
*
* ```html
* <my-button>Click me</my-button>
* ```
*/
class MyButton extends HTMLElement {}

External Markdown Files

For longer or separately maintained documentation, use @markdown to pull in an external Markdown file. The content is appended to the JSDoc description in the order the tags appear. You can use this tag multiple times in the same block.

/**
* Short inline description.
*
* @markdown {./docs/overview.md}
* @markdown {./docs/usage.md}
*/
class MyComponent extends HTMLElement {}

If the JSDoc block has an inline description, it appears first, followed by each referenced file in order. This is equivalent to writing all the Markdown inline, but keeps the source file clean.

Code Examples

Use @example to include runnable or illustrative code snippets.

/**
* @example
* const el = document.createElement("my-component");
* el.value = "hello";
* document.body.appendChild(el);
*/
class MyComponent extends HTMLElement {}

Properties

The analyzer scans for public properties and automatically extracts the name, accessors (getter, setter, or both), type, and default value. The tags below let you provide additional information or correct cases where automatic inference falls short.

@prop | @property

Use this to manually specify the type when the analyzer cannot infer it, such as with complex or conditional types.

class MyComponent extends HTMLElement {
/**
* The current value.
*
* @property {{ name: string; age: number }}
*/
value: ComplexType = {};
}

@default

Override or supplement the inferred default value.

class MyComponent extends HTMLElement {
/**
* Animation duration in milliseconds.
*
* @default 300
*/
duration = 300;
}

@required

Mark a property as required. Required properties become required JSX props in the generated TypeScript types for frameworks like React and Preact.

class MyComponent extends HTMLElement {
/**
* The item identifier.
*
* @required
*/
itemId = "";
}

@attr | @attribute

By default, properties are linked to attributes of the same name. Use @attr to change this in three ways:

Override the attribute type without changing the property type:

class MyComponent extends HTMLElement {
/** @attr {string} */
value = 0;
}

Add a custom attribute description without renaming it. Use - as a name placeholder:

class MyComponent extends HTMLElement {
/** @attr - Custom description for this attribute. */
value = 0;
}

Link to a differently named attribute with an optional description:

class MyComponent extends HTMLElement {
/** @attr data-value The value exposed as a data attribute. */
value = 0;
}

@method | @function | @func

Use this when a property holds a function that the analyzer does not automatically recognize as a method. You can optionally include the type signature.

class MyComponent extends HTMLElement {
/**
* Opens the dialog.
*
* @function {() => void}
*/
open = () => {};
}

@event | @fires | @emits

Marks a property as an event. This approach is not recommended — prefer defining events through the _events static property instead (see Events).

class MyComponent extends HTMLElement {
/**
* Fired when the value changes.
*
* @event
*/
onChange = () => new CustomEvent("change");
}

Ignoring Properties

To exclude a single property, add @ignore or @internal to its JSDoc block:

class MyComponent extends HTMLElement {
/** @ignore */
internalCache = null;
}

To exclude multiple properties at once, use @ignoreIn on the class JSDoc:

/** @ignoreIn {properties} value,anotherProperty */
class MyComponent extends HTMLElement {}

Attributes

The analyzer extracts attribute names from the static observedAttributes getter. Type and description are inherited from the property whose name matches the attribute. For example, the attribute close-button inherits its documentation from the property closeButton.

class MyComponent extends HTMLElement {
static get observedAttributes() {
return ["value"] as const;
}
/** The selected value. */
value: "foo" | "bar" | "baz" = "foo";
}

Documenting Unobserved Attributes

If an attribute is not listed in observedAttributes or does not correspond to a property, document it directly on the class using @attr or @attribute:

/**
* @attr {boolean} open Whether the panel is open.
* @attr {"foo" | "bar" | "baz"} value The active value.
*/
class MyComponent extends HTMLElement {}

Ignoring Attributes

/** @ignoreIn {attributes} open,value */
class MyComponent extends HTMLElement {}

Methods

The analyzer scans for public methods and extracts the name and type where possible. Because method signatures can be complex to infer, explicit type annotations are recommended.

class MyComponent extends HTMLElement {
/** Opens the panel. */
readonly open: (animate: boolean) => void = (animate = true) => {
// ...
};
}

Specifying the Type Manually

Use @method, @function, or @func when the analyzer cannot determine the type:

class MyComponent extends HTMLElement {
/**
* Forces a re-render.
*
* @function {() => void}
*/
readonly refresh = () => {
// ...
};
}

Documenting Undetected Methods

If the analyzer misses a method entirely, declare it on the class JSDoc:

/** @function {() => void} refresh Forces a re-render. */
class MyComponent extends HTMLElement {}

Ignoring Methods

To exclude a single method, add @ignore or @internal to its JSDoc block:

class MyComponent extends HTMLElement {
/** @ignore */
close = () => {};
}

To exclude multiple methods at once, use @ignoreIn on the class JSDoc:

/** @ignoreIn {methods} open,close */
class MyComponent extends HTMLElement {}

Events

The preferred way to document events is through the _events static property. The analyzer reads this property to extract event names, payload types, and JSDoc descriptions.

class MyComponent extends HTMLElement {
static readonly _events = {
/**
* Fired when the panel opens.
*
* **Detail:** The element that triggered the open.
*/
_opened: (detail: HTMLElement) => new CustomEvent("opened", { detail }),
/**
* Fired when the user clicks the trigger.
*
* **Cancelable:** Preventing this event stops the panel from toggling.
*/
_clicked: () => new CustomEvent("clicked", { cancelable: true }),
};
}

Documenting Undetected Events

If the analyzer misses an event, declare it on the class JSDoc using @event, @fires, or @emits:

/**
* @event opened Fired when the panel opens.
* @event clicked Fired when the user clicks the trigger.
*/
class MyComponent extends HTMLElement {}

Ignoring Events

To exclude a single event, add @ignore or @internal to its JSDoc block:

class MyComponent extends HTMLElement {
static readonly _events = {
/** @ignore */
_clicked: () => new CustomEvent("clicked", { cancelable: true }),
};
}

To exclude multiple events at once, use @ignoreIn on the class JSDoc:

/** @ignoreIn {events} opened,clicked */
class MyComponent extends HTMLElement {}

Slots

The analyzer extracts slot names from <slot> elements in the component template. Use @slot comments to add descriptions.

Use - as the name placeholder to have the analyzer infer the name from the element below. For dynamic slots that are not present in the static template, add the comment before the template block.

<!-- @slot dynamic A slot rendered conditionally at runtime. -->
<!-- @slot - Description for the "header" slot. -->
<slot name="header"></slot>
<!-- @slot - Description for the default slot. -->
<slot></slot>

Documenting Slots Manually

/**
* @slot header Content placed in the header area.
* @slot default The main content area.
*/
class MyComponent extends HTMLElement {}

Ignoring Slots

To exclude a single slot, add @ignore or @internal to its JSDoc block:

<!-- @ignore -->
<slot name="header"></slot>

To exclude multiple slots at once, use @ignoreIn on the class JSDoc:

/** @ignoreIn {slots} header,footer */
class MyComponent extends HTMLElement {}

CSS Parts

The analyzer extracts part names from part and exportparts attributes in the template. Use @part comments to add descriptions.

Use - as the name placeholder to infer the name from the element below. For dynamic parts not present in the static template, add the comment before the template block.

<!-- @part container The outermost wrapper element. -->
<!-- @part - The SVG root element. -->
<svg part="svg">
<!-- @part - The track circle. -->
<circle part="track" />
<!-- @part - The fill circle. -->
<circle part="fill" />
</svg>

Documenting Parts Manually

/**
* @part svg The SVG root element.
* @part track The track circle.
* @part fill The fill circle.
*/
class MyComponent extends HTMLElement {}

Ignoring Parts

To exclude a single part, add @ignore or @internal to its JSDoc block:

<!-- @ignore -->
<svg part="svg"></svg>

To exclude multiple parts at once, use @ignoreIn on the class JSDoc:

/** @ignoreIn {cssParts} svg,track */
class MyComponent extends HTMLElement {}

CSS Variables

The analyzer extracts CSS custom properties from :host and .root selectors in the component stylesheet. Add a description by placing a comment directly above the variable declaration.

:host {
/* Controls the animation duration. */
--wcp-anim-duration: 350ms;
}

Renaming Variables

Use @name to override the documented name of a variable. Use @name {from-value} to use the variable referenced in the property value as the documented name.

.root {
/*
Documented as `--wcp-base-2`.
@name {from-value}
*/
--background-color: var(--wcp-base-2);
/*
Documented as `--wcp-base-1`.
@name --wcp-base-1
*/
--trigger-background: var(--wcp-base-1);
}

Documenting Variables Manually

Use @cssVar, @cssProp, or @cssProperty on the class JSDoc:

/**
* @cssVar --wcp-color The primary text color.
* @cssVar --wcp-radius The border radius applied to the root element.
*/
class MyComponent extends HTMLElement {}

Ignoring Variables

To exclude a single variable, add @ignore or @internal to its JSDoc block:

.root {
/* @ignore */
--background-color: var(--wcp-base-2);
}

To exclude multiple variables at once, use @ignoreIn on the class JSDoc:

/** @ignoreIn {cssVars} color,size */
class MyComponent extends HTMLElement {}

CSS States

The analyzer detects CSS custom states by scanning for internals.states.add() calls in the class body. State names are extracted automatically. Add descriptions using @cssState on the class JSDoc.

/**
* @cssState open Applied when the panel is open.
* @cssState loading Applied while async content is loading.
*/
class MyComponent extends HTMLElement {
#internals = this.attachInternals();
#setOpen(value: boolean) {
if (value) {
this.#internals.states.add("open");
} else {
this.#internals.states.delete("open");
}
}
}

Ignoring CSS States

/** @ignoreIn {cssStates} open,loading */
class MyComponent extends HTMLElement {}