A custom element to
define custom elements.

Declarative Custom Elements implementation.
Typed props, scoped styles, shadow DOM, lifecycle events.
Components for alpine, petite-vue, sprae, template-parts etc.

<script src="unpkg.com/define-element">
0
boilerplate
0
deps
0
build

Web-components by example

Define custom elements in HTML. No JS required.

index.html
<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>
Result

No API

The API is markup

Typed Props

Declare with optional types: count:number="0". Reflect to attributes. onpropchange callback.

Scoped Styles

CSS nesting for light DOM, adoptedStyleSheets for shadow. :host works in both.

Shadow DOM

shadowrootmode for encapsulation. Native slots for content projection.

Lifecycle

onconnected, ondisconnected, onpropchange. Script runs once per instance via injection (no eval).

Typed props

Declare attributes with optional types (auto-detected).

<x-widget count:number="0" label:string="Click me" active:boolean>
TypeCoercionDefault
:stringString(v)""
:numberNumber(v)0
:booleantrue unless "false"false
:datenew Date(v)null
:arrayJSON.parse(v)[]
:objectJSON.parse(v){}
(none)auto-detectas-is

Connect, disconnect, update

Script runs once on first connect. Hooks fire on connect, disconnect, prop change.

clock.html
<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>
Result

Template engine

Works with sprae, Alpine, petite-vue, template-parts.

sprae Alpine petite-vue W3C template-parts

    

Why this exists

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.

Read the docs →