Skip to content
ore logoOreUI Primitives
Functional custom-element authoring with typed props, reactive templates, lifecycle helpers, observers, and testing utilities.
Version
v2.0.8
Size
9.4 KB gzip
Dependencies
Zero dependencies
Browser
defineprophtmlcssref View all 38 exports

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 size9.4 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 @vielzeug/ripple
sh
npm install @vielzeug/ore @vielzeug/ripple
sh
yarn add @vielzeug/ore @vielzeug/ripple

Quick Start

ts
import { computed, signal } from '@vielzeug/ripple';
import { bind, css, define, html, onMounted, 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) {
    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(props) takes only props and returns an HTMLResult directly: return html\...``
  • Lifecycle hooks — onMounted, onCleanup, onEvent, onElement, watchEffect — plain functions imported from @vielzeug/ore, called directly from setup() or any composable it calls
  • Directives: each (keyed reactive list rendering), classMap, styleMap, when, live, unsafeHtml
  • Host bindings via bind({ attr, class, style, on }) — pass { target: el } to bind any off-host element
  • Reactive ARIA sync via bind({ aria }, { target }) — applies aria-* attributes reactively to any element, auto-cleanup on disconnect
  • Context via provide(key, value) / inject(key); typed emit/slots via useEmit<Emits>() / useSlots<SlotNames>()
  • Form-associated useField() and observer helpers are root exports
  • Testing utilities (@vielzeug/ore/testing) — mount, renderHook, flush, cleanup
  • Generic testing utilities (scoped queries, named event dispatchers, and async waits) are exported by @vielzeug/assay
  • Debug utilities (@vielzeug/ore/testing) — debugFlush() for diagnosing update timing

Package Entry Points

ImportPurpose
@vielzeug/oreAll browser runtime APIs: components, directives, useField, and observers
@vielzeug/ore/testingOre-specific mounting, lifecycle flushing, hooks, cleanup, and form internals
@vielzeug/assayGeneric DOM events, scoped queries, and async waiting

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.