Skip to content
ore logoOreUI Primitives
Functional custom-element authoring with typed props, reactive templates, lifecycle helpers, observers, and testing utilities.
v1.0.28.0 KB gzipBrowser
defineprophtmlcssrefcreateContextinjectinjectStrict +9 more →

Why Ore?

Ore keeps custom elements functional and signal-driven while giving you direct control over templates, lifecycle hooks, host bindings, and form-associated behavior.

ts
// Before — vanilla custom element boilerplate
class MyCounter extends HTMLElement {
  #count = 0;
  connectedCallback() {
    this.attachShadow({ mode: 'open' });
    this.#render();
  }
  #render() {
    this.shadowRoot!.innerHTML = `<button>${this.#count}</button>`;
    this.shadowRoot!.querySelector('button')!.onclick = () => {
      this.#count++;
      this.#render();
    };
  }
}
customElements.define('my-counter', MyCounter);

// After — Ore
import { signal } from '@vielzeug/ripple';
import { define, html } from '@vielzeug/ore';

define('my-counter', {
  setup() {
    const count = signal(0);
    return html`<button @click=${() => count.value++}>${count}</button>`;
  },
});
FeatureOreLitStencil
Bundle size8.0 KB~12 kB~60 kB+ toolchain
Signal-first runtime (separate signals package)
Functional component setupPartial
Typed prop helpersPartial
Host binding helpersPartialPartial
Form-associated helpersManualPartial
Zero dependencies

Use Ore when you want typed, signal-driven custom elements with minimal runtime overhead and no framework lock-in.

Consider Lit when you need a mature ecosystem with wide community adoption and don't need signal-based reactivity.

Installation

sh
pnpm add @vielzeug/ore
sh
npm install @vielzeug/ore
sh
yarn add @vielzeug/ore

Quick Start

ts
import { computed, signal } from '@vielzeug/ripple';
import { css, define, html, prop } from '@vielzeug/ore';

define('my-counter', {
  props: {
    label: prop.string('Count'),
    step: prop.number(1),
  },
  styles: [
    css`
      :host {
        display: inline-grid;
        gap: 0.5rem;
      }
    `,
  ],
  setup(props, { bind, onMounted }) {
    const count = signal(0);
    const doubled = computed(() => count.value * 2);

    bind({ class: { 'is-positive': () => count.value > 0 } });

    onMounted(() => console.log('mounted'));

    return html`
      <button @click=${() => (count.value += props.step.value)}>${props.label}: ${count}</button>
      <p>Doubled: ${doubled}</p>
    `;
  },
});

Features

  • Signal-first runtime with signal, computed, watch, batch from @vielzeug/ripple — import them directly
  • Functional component authoring via define(tag, { props, setup, styles, formAssociated })
  • Props via prop.* helpers (prop.string, prop.number, prop.bool, prop.oneOf, prop.json, prop.data) or raw PropDef objects
  • Setup returns an HTMLResult directly: return html\...``
  • Lifecycle hooks — onMounted, onCleanup, onEvent, onElement, effect — accessed through the setup context bag (ctx)
  • Directives: each (keyed reactive list rendering), classMap, styleMap, when, model, raw
  • Host bindings via ctx.bind({ attr, class, style, on }) — pass { target: el } to bind any off-host element
  • Reactive ARIA sync via ctx.aria(target, config) — applies aria-* attributes reactively to any element, auto-cleanup on disconnect
  • Form-associated helpers: useField(), createFormContext() — first-class public APIs for form-aware components
  • Observers (@vielzeug/ore/observers)
  • Testing utilities (@vielzeug/ore/testing) — mount, renderHook, fire, user, waitFor, cleanup
  • Debug utilities (@vielzeug/ore/devtools) — debugFlush() for diagnosing update timing

Package Entry Points

ImportPurpose
@vielzeug/oreCore component API and utilities (define, prop, html, css, context, forms)
@vielzeug/ore/devtoolsdebugFlush — verbose flush for timing diagnostics (dev only)
@vielzeug/ore/directiveseach, when, model, live, raw, classMap, styleMap
@vielzeug/ore/observersresizeObserver, intersectionObserver, mediaObserver, mutationObserver
@vielzeug/ore/testingmount, fire, user, waitFor, cleanup, and helpers

Documentation

See Also

  • Refine for prebuilt accessible components powered by Ore.
  • Ripple for reactive state used inside Ore components.
  • Forge for typed form state that integrates with Ore.