Angular
A custom element built with elements-kit is just class extends HTMLElement registered via customElements.define. Angular renders it fine at runtime, but you need to tell the Angular compiler to allow unknown elements and add InstanceProps<I> annotations to get typed element references in TypeScript.
// shared element β built once, used anywhereimport { defineElement } from "elements-kit/custom-elements";import { reactive } from "elements-kit/signals";
export class XCounter extends HTMLElement { @reactive() count = 0;}
defineElement("x-counter", XCounter);Allow custom elements in the template compiler
By default Angular raises an error for unknown element tags. Add CUSTOM_ELEMENTS_SCHEMA to the schemas array of the component (standalone) or NgModule that uses the element:
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
@Component({ selector: "app-root", template: `<x-counter [count]="5"></x-counter>`, schemas: [CUSTOM_ELEMENTS_SCHEMA],})export class AppComponent {}CUSTOM_ELEMENTS_SCHEMA suppresses the unknown-element error for any tag that contains a hyphen. You only need it on the module or standalone component that contains the custom element β not globally.
Source the prop shape from the class
Two helpers exported from elements-kit/jsx-runtime:
| Helper | Shape | Use when |
|---|---|---|
InstanceProps<I> | Public instance fields only (drops the HTMLElement surface) | You want to type just the user-defined props |
ElementProps<C> | Full elements-kit JSX surface β attrs, fields, events from static events, slots from [SLOTS], children | Your element declares typed events or slots and you want callers to see them |
Type element references
Angular doesnβt autocomplete custom element props in templates β CUSTOM_ELEMENTS_SCHEMA is a runtime-only allowlist. To get typed access to an instance, annotate @ViewChild or document.querySelector with the class directly:
import { Component, ViewChild, ElementRef, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";import type { InstanceProps } from "elements-kit/jsx-runtime";import { XCounter } from "./x-counter";
@Component({ selector: "app-root", template: `<x-counter #counter [count]="5"></x-counter>`, schemas: [CUSTOM_ELEMENTS_SCHEMA],})export class AppComponent { // ref is typed as XCounter β access reactive fields directly @ViewChild("counter") counter!: ElementRef<XCounter>;
increment() { this.counter.nativeElement.count++; }}InstanceProps<XCounter> is useful as a type annotation when you need to describe the settable surface of the element β for example, when building a wrapper component or writing a helper function:
import type { InstanceProps } from "elements-kit/jsx-runtime";import { XCounter } from "./x-counter";
function applyProps(el: XCounter, props: InstanceProps<XCounter>) { Object.assign(el, props);}Typed document.querySelector and createElement
Independent of any framework, augmenting the global HTMLElementTagNameMap gives you typed lookups in plain DOM code:
declare global { interface HTMLElementTagNameMap { "x-counter": XCounter; }}
const el = document.querySelector("x-counter"); // XCounter | nullWorth adding to the same file as the element class.