Marko
A custom element built with elements-kit is just class extends HTMLElement registered via customElements.define. Marko renders it fine at runtime β custom elements with a hyphen in the tag name are passed through as-is. TypeScript support for Marko template types is provided by @marko/language-tools and is evolving; the most reliable typing surface today is HTMLElementTagNameMap for DOM queries.
// 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);Source the prop shape from the class
Three type helpers exported from elements-kit/custom-elements read the raw platform surface off the class. All three take the constructor (typeof XCounter); PropertiesOf also accepts an instance type.
| Helper | Shape | Use when |
|---|---|---|
PropertiesOf<typeof XCounter> | Public instance fields, all optional β the HTMLElement surface is dropped; Slot-typed fields accept a Node | Typing property assignments β prop bindings, wrappers, helper functions |
AttributesOf<typeof XCounter> | Every key of static [ATTRIBUTES], valued string | null | Typing the raw HTML attribute surface β setAttribute, server-rendered markup |
EventsOf<typeof XCounter> | The static events map, verbatim β event name to event type | Typing addEventListener, framework event bindings, HTMLElementEventMap augmentations |
Use the element in a template
Import the element registration before the template renders client-side. In a Marko component, put the import in <script> or the componentβs class body:
import "./x-counter"; // registers the custom element
<x-counter count=5 />Typed document.querySelector and createElement
Augmenting the global HTMLElementTagNameMap gives you typed DOM lookups in any TypeScript file β Marko template logic, server routes, or client scripts:
import type { XCounter } from "./src/x-counter";
declare global { interface HTMLElementTagNameMap { "x-counter": XCounter; }}Make sure this file is covered by your tsconfig.jsonβs include array.
const el = document.querySelector("x-counter"); // XCounter | nullPropertiesOf<typeof XCounter> is useful as a type annotation when writing helper functions that operate on the element:
import type { PropertiesOf } from "elements-kit/custom-elements";import { XCounter } from "./x-counter";
function applyProps(el: XCounter, props: PropertiesOf<typeof XCounter>) { Object.assign(el, props);}