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>`;
},
});| Feature | Ore | Lit | Stencil |
|---|---|---|---|
| Bundle size | 8.0 KB | ~12 kB | ~60 kB+ toolchain |
| Signal-first runtime | |||
| Functional component setup | Partial | ||
| Typed prop helpers | Partial | ||
| Host binding helpers | Partial | Partial | |
| Form-associated helpers | Manual | Partial | |
| 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/oresh
npm install @vielzeug/oresh
yarn add @vielzeug/oreQuick 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,batchfrom@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 rawPropDefobjects - Setup returns an
HTMLResultdirectly: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)— appliesaria-*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
| Import | Purpose |
|---|---|
@vielzeug/ore | Core component API and utilities (define, prop, html, css, context, forms) |
@vielzeug/ore/devtools | debugFlush — verbose flush for timing diagnostics (dev only) |
@vielzeug/ore/directives | each, when, model, live, raw, classMap, styleMap |
@vielzeug/ore/observers | resizeObserver, intersectionObserver, mediaObserver, mutationObserver |
@vielzeug/ore/testing | mount, fire, user, waitFor, cleanup, and helpers |