Skip to main content
Version: v5 (Preview)

Typed Components

On every build, Stencil writes src/components.d.ts with types for each of your components. For a my-toggle component, it declares:

  • HTMLMyToggleElement: the element, with its props and methods. It's global, like HTMLButtonElement, so it needs no import.
  • MyToggleCustomEvent<T>: the type of the component's events.
  • JSX types, so <my-toggle> is type-checked wherever you render it.

Referencing Elements​

Use the HTML{Tag}Element interfaces wherever you hold a component element:

@Element() el: HTMLMyPanelElement;

private toggle?: HTMLMyToggleElement;

render() {
return <my-toggle ref={(el) => (this.toggle = el)} />;
}

querySelector() returns the right interface for a tag name on its own, so its props and methods are typed:

const toggle = this.el.querySelector('my-toggle'); // HTMLMyToggleElement | null
toggle?.toggle();

Typed Events​

An event's type comes from its @Event() declaration. Here my-toggle declares that its myChange event's detail has a checked boolean:

my-toggle.tsx
@Event() myChange: EventEmitter<{ checked: boolean }>;

Event handlers in JSX are then typed from that detail:

my-panel.tsx
<my-toggle onMyChange={(ev) => (this.open = ev.detail.checked)} />

To type a handler declared elsewhere, import the event type from components.d.ts:

import { MyToggleCustomEvent } from '../../components';

private onChange = (ev: MyToggleCustomEvent<{ checked: boolean }>) => {
this.open = ev.detail.checked;
};

addEventListener() on a component element is typed the same way.

Type-Checked JSX​

Rendering one of your components in another component's render() checks its props: a wrong type, or a missing required prop, is a compile error:

<my-toggle checked="yes" /> // Type 'string' is not assignable to type 'boolean'

In Tests​

The same interfaces type elements in tests. In a Playwright test, for example:

await page.locator('my-toggle').evaluate((el: HTMLMyToggleElement) => (el.checked = true));

For Consumers​

components.d.ts is for your own project. What consumers get comes from other outputs:

  • The types output target publishes the same types in dist/types, for consumers using TypeScript. See Editor Support for how their apps pick them up.
  • Framework Types make your tags type-checked in React, Vue, Solid, Svelte, and Preact templates.
  • The React, Vue, and Angular wrappers give consumers typed framework components.

Committing components.d.ts​

Stencil regenerates the file on every build, so committing it is optional:

  • Committed, types work straight after cloning, before the first build, and a pull request's diff shows changes to your components' public API.
  • Ignored (add it to .gitignore), there's no generated file to keep in sync or resolve conflicts in. Editors show missing types on a fresh clone until the first build.