Skip to content

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 anywhere
import { 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.

HelperShapeUse when
PropertiesOf<typeof XCounter>Public instance fields, all optional β€” the HTMLElement surface is dropped; Slot-typed fields accept a NodeTyping property assignments β€” prop bindings, wrappers, helper functions
AttributesOf<typeof XCounter>Every key of static [ATTRIBUTES], valued string | nullTyping the raw HTML attribute surface β€” setAttribute, server-rendered markup
EventsOf<typeof XCounter>The static events map, verbatim β€” event name to event typeTyping 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:

x-counter.marko
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:

types/elements.d.ts
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 | null

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

See also