Skip to content

Instantly share code, notes, and snippets.

@hnordt
Created November 16, 2025 19:04
Show Gist options
  • Select an option

  • Save hnordt/8eb447ac8d155a516b4e6d17fe5873aa to your computer and use it in GitHub Desktop.

Select an option

Save hnordt/8eb447ac8d155a516b4e6d17fe5873aa to your computer and use it in GitHub Desktop.
A type-safe, semantic custom counter element built with Custom Elements, the Invoker Commands API, and preact-render-to-string.
import { renderToStaticMarkup } from "preact-render-to-string";
declare global {
namespace preact.JSX {
interface IntrinsicElements {
"el-counter": preact.HTMLAttributes<HTMLElement>;
}
}
}
customElements.define(
"el-counter",
class extends HTMLElement {
#state = {
count: 0,
};
#commands = {
increment: `--${crypto.randomUUID()}`,
decrement: `--${crypto.randomUUID()}`,
};
connectedCallback() {
this.id = crypto.randomUUID();
this.addEventListener("command", this.#onCommand);
this.#render();
}
disconnectedCallback() {
this.removeEventListener("command", this.#onCommand);
}
#onCommand(event: CommandEvent) {
if (event.command === this.#commands.increment) {
this.#state.count++;
}
if (event.command === this.#commands.decrement) {
this.#state.count--;
}
this.#render();
}
#render() {
this.innerHTML = renderToStaticMarkup(
<div
role="group"
aria-label={this.getAttribute("aria-label") ?? "Counter"}
>
<button
id={this.#commands.decrement}
type="button"
commandfor={this.id}
command={this.#commands.decrement}
>
-
</button>
<output
for={`${this.#commands.decrement} ${this.#commands.increment}`}
>
{this.#state.count}
</output>
<button
id={this.#commands.increment}
type="button"
commandfor={this.id}
command={this.#commands.increment}
>
+
</button>
</div>,
);
}
},
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment