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;
}

@reflect | @no-reflect

The analyzer marks a property as reflecting when its setter calls this.setAttribute or this.toggleAttribute with the property’s attribute name. Use @reflect to mark reflection the analyzer cannot see, with an optional attribute name when it differs from the property’s attribute. Use @no-reflect to disable the detection.

class MyComponent extends HTMLElement {
/** @reflect */
set vertical(value: boolean) {
this._reflect(value); // reflection through a helper, not detectable
}
/** @no-reflect */
set value(value: string) {
this.setAttribute("value", value); // not treated as reflection
}
}

@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, the type slot holds the type of event.detail. The property name is used as the event name, so it has to match the name the event is constructed with. 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 {{ value: string }}
*/
change = (value: string) => new CustomEvent("change", { detail: { value } });
}

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 the event name, the type of event.detail, and the cancelable, bubbles, and composed options from the source.

class MyComponent extends HTMLElement {
static readonly _events = {
/** Fired when the panel opens. */
_opened: (detail: HTMLElement) => new CustomEvent("opened", { detail, bubbles: true }),
};
}

The event above is documented as a bubbling CustomEvent<HTMLElement> without a single tag. The tags below describe what the source cannot express, and override what the analyzer inferred.

@detail | @eventDetail | @payload | @eventPayload

Describe the payload. The type slot overrides the inferred type, and is the type of event.detail, not the type of the event. Use - as the name placeholder.

class MyComponent extends HTMLElement {
static readonly _events = {
/**
* Fired before the value changes.
*
* @detail - The proposed `value` and the `previous` one.
*/
_beforeChange: (detail: ChangeDetail) => new CustomEvent("beforechange", { detail }),
/**
* Fired when an item is selected.
*
* @detail {{ index: number }} - The index of the selected item.
*/
_selected: detail => new CustomEvent("selected", { detail }), // untyped parameter
};
}

@cancelable | @no-cancelable

An event is cancelable when it is constructed with cancelable: true. Use @cancelable to document what preventing the event does, or to mark events the analyzer cannot see. Use @no-cancelable to clear it, and - as the name placeholder.

class MyComponent extends HTMLElement {
static readonly _events = {
/**
* Fired when the user clicks the trigger.
*
* @cancelable - Stops the panel from toggling.
*/
_clicked: () => new CustomEvent("clicked", { cancelable: true }),
};
}

@bubbles | @composed

Both are read from the event options. Use @bubbles and @composed to mark propagation the analyzer cannot see, and @no-bubbles and @no-composed to clear it.

class MyComponent extends HTMLElement {
static readonly _events = {
/**
* Fired when the panel opens.
*
* @bubbles
* @composed
*/
_opened: () => new CustomEvent("opened", eventOptions), // options from a shared object
};
}

Documenting Undetected Events

If the analyzer misses an event, declare it on the class JSDoc using @event, @fires, or @emits. The type slot holds the type of event.detail.

/**
* @event opened Fired when the panel opens.
* @event {{ reason: string }} closed Fired when the dialog closes.
*/
class MyComponent extends HTMLElement {}

The other event tags take the event name in the same slot, and may appear in any order:

/**
* @event {{ reason: string }} closed Fired when the dialog closes.
* @cancelable closed Keeps the dialog open.
* @bubbles closed
*/
class MyComponent extends HTMLElement {}

Tags always win over what the analyzer reads from the source, and tags written next to the event win over tags on the class.

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. */
--sv-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 `--sv-base-2`.
@name {from-value}
*/
--background-color: var(--sv-base-2);
/*
Documented as `--sv-base-1`.
@name --sv-base-1
*/
--trigger-background: var(--sv-base-1);
}

Documenting Variables Manually

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

/**
* @cssVar --sv-color The primary text color.
* @cssVar --sv-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(--sv-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 {}