Skip to content
ore logoOreUI Primitives
Functional custom-element authoring with typed props, reactive templates, lifecycle helpers, and testing utilities.
Version
v26.10.0
Size
9.6 KB gzip
Dependencies
Zero dependencies
Browser
defineprophtmlcssref View all 34 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.6 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 explicit aria-* keys in bind({ attr }, { target }), with automatic 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

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.