SkillAgentSearch skills...

Element

Fast and simple custom elements.

Install / Use

/learn @lume/Element

README

@lume/element <!-- omit in toc -->

Easily and concisely write Custom Elements with simple templates and reactivity.

Use the custom elements on their own in plain HTML or vanilla JavaScript, or in Vue, Svelte, Solid.js, Stencil.js, React, and Preact, with full type checking, autocompletion, and intellisense in all the template systems of those frameworks, in any IDE that supports TypeScript such as VS Code.

Write your elements once, then use them in any app, with a complete developer experience no matter which base component system your app uses.

<h4><code><strong>npm install @lume/element</strong></code></h4>

:bulb:Tip:

If you are new to Custom Elements, first learn about the basics of Custom Element APIs available natively in browsers. Lume Element simplifies the creation of Custom Elements compared to writing them with vanilla APIs, but sometimes vanilla APIs are all that is needed.

Live demos <!-- omit in toc -->

Table of contents <!-- omit in toc -->

Cliché Click Counter Example

Define a <click-counter> element:

import {Element, element, numberAttribute} from '@lume/element'
import html from 'solid-js/html'
import {createEffect} from 'solid-js'

@element
class ClickCounter extends Element {
  @numberAttribute count = 0

  template = () => html`<button onclick=${() => this.count++}>Click! (count is: ${() => this.count})</button>`

  css = `
		button {
			border: 2px solid deeppink;
			margin: 5px;
		}
	`

  // Log the `count` any time it changes:
  @effect logCount() {
    console.log('count is:', this.count)
  }
}

Use the <click-counter> in a plain HTML file:

<body>
  <click-counter></click-counter>

  <!-- Manually set the `count` value in HTML: -->
  <click-counter count="100"></click-counter>

  <script type="module">
    import './click-counter.js'

    // Manually set the `count` value in JS:
    document.querySelector('click-counter').count = 200
  </script>
</body>

Example on CodePen (without decorators)

[!Note] Once decorators land in browsers, the above example will work out of the box as-is without compiling, but for now a compile step is needed for using decorators.

JSX can be used for the template of an element, but that will always require compiling:

template = () => <button> Click! (count is: {this.count}) </button>

Further examples below show how to define elements without decorators or JSX, which works today without a compiler.

Use the <click-counter> in another element's template,

import {Element, element} from '@lume/element'
import html from 'solid-js/html'
import {signal} from 'classy-solid'

@element('counter-example')
class CounterExample extends Element {
  @signal count = 50 // Not an attribute, only a signal.

  template = () => html`<click-counter count=${() => this.count}></click-counter>`
}

document.body.append(new CounterExample())

Use <click-counter> in a plain function component (i.e. a Solid.js component):

// At this point this, this boils down to plain Solid.js code (`@lume/element` comes
// with `solid-js`)

import {createSignal} from 'solid-js'
import html from 'solid-js/html'

function CounterExample() {
  const [count, setCount] = createSignal(50)

  return html`<click-counter count=${count()}></click-counter>`
}

document.body.append(CounterExample())

Intro

Custom Elements (also known as Web Components are a feature of browsers that allow us to define new HTML elements that the browser understands in the same way as built-in elements like <div> or <button>. They are very useful for organizaing web apps into separately and sometimes re-usable pieces (elements).

If that flew over your head then you might first want to try a beginner HTML tutorial. You will also need to some basic knowledge of JavaScript.

@lume/element provides a set of features that make it easier to manipulate elements and to define new custom elements and easily compose them together into an application.

With @lume/element we can create custom elements that have the following features:

  • Reactive instance properties that receive values from element attributes of the same name (but dash-cased).
  • Declarative templates, written with JSX or html template tag, that automatically update when reactive instance properties are used in the templates.
  • Scoped styling with or without a ShadowRoot.
  • Decorators for concise element definitions.
<details><summary>A more detailed feature description:</summary>
  • Element attributes are defined with @attribute decorators on class fields.
    • Class fields decorated with @attribute receive values from HTML attributes (with the same name but dash-cased) when the HTML attribute values change.
    • Decorators are powered by classy-solid: utilities for using Solid.js patterns on classes, such as the @signal decorator for making class fields reactive (backed by Solid signals). Decorators from @lume/element compose the @signal decorator to make properties be reactive.
    • As decoraators are not out in browsers yet, an alternative non-decorator API can be used, which does not require a build.
  • Each custom element can have an HTML template that automatically updates the DOM when any reactive variables used in the template changes.
    • Templates can be written in the form of HTML-like markup inside JavaScript called JSX, specifically the JSX flavor from Solid.js. This requires a build step.
    • Templates can also be written using Solid's html template string tag, which does not require a build step.
    • When a template updates, the whole template does not re-run, only the part of the template where a variable changed is updated, and only that particular piece of DOM gets modified. There is no (expensive) virtual DOM diffing.
    • Because changes to HTML attributes on an element map to properties backed by signals on the element instance, this will cause the custom element's template to upd
View on GitHub
GitHub Stars185
CategoryDevelopment
Updated6d ago
Forks5

Languages

TypeScript

Security Score

100/100

Audited on Mar 25, 2026

No findings