Declarative Custom Elements implementation.
Typed props, scoped styles, shadow DOM, lifecycle events.
Components for alpine, petite-vue, sprae, template-parts etc.
Define custom elements in HTML. No JS required.
<define-element> <x-counter count:number="0"> <template> <button id="btn"> Clicks: <span id="n">0</span> </button> </template> <script> this.onconnected = () => { let n = this.querySelector('#n') this.querySelector('#btn').onclick = () => n.textContent = ++this.count } </script> </x-counter> </define-element> <!-- use it --> <x-counter></x-counter> <x-counter count="10"></x-counter>
The API is markup
Declare with optional types: count:number="0". Reflect to attributes. onpropchange callback.
CSS nesting for light DOM, adoptedStyleSheets for shadow. :host works in both.
shadowrootmode for encapsulation. Native slots for content projection.
onconnected, ondisconnected, onpropchange. Script runs once per instance via injection (no eval).
Declare attributes with optional types (auto-detected).
<x-widget count:number="0" label:string="Click me" active:boolean>
Script runs once on first connect. Hooks fire on connect, disconnect, prop change.
<define-element> <x-clock> <template> <time id="t"></time> </template> <script> let id, t const tick = () => t.textContent = new Date().toLocaleTimeString() this.onconnected = () => { t = this.querySelector('#t') tick(); id = setInterval(tick, 1000) } this.ondisconnected = () => clearInterval(id) </script> <style> :host { font-family: monospace } </style> </x-clock> </define-element>
Works with sprae, Alpine, petite-vue, template-parts.
The W3C Declarative Custom Elements proposal has stalled for years. JS-side solutions require build tools and class boilerplate. The declarative CE space is a graveyard.
The gap: no lightweight way to define a custom element as HTML. Paste a <define-element> block into any page, CMS, or markdown and it works.
This ~200-line polyfill is evidence that the proposal is implementable and useful. Ship it natively.