Skip to content

Frameworks

Relements is framework-agnostic with no wrappers. There is no @relements/react or @relements/vue — the published package is just @relements/core, and the same surface works everywhere:

  • .re-* classes on native elements — the zero-JS styling baseline.
  • data-* attributes — declarative hooks that behaviors read.
  • re-* CustomEvents — bubbling DOM events you listen to with addEventListener.
  • enhance*(root) → { destroy() } behaviors — optional JS that wires a subtree and tears itself down.
  • <re-*> light-DOM custom elements — self-registering, self-managing tags.

Because all five are plain web-platform primitives — classes, attributes, DOM events, functions, and custom elements — every framework already speaks them. The only thing that differs per framework is where you call enhance*() on mount and destroy() on unmount, plus a line or two of config so the framework lets <re-*> tags and their events through.

Every framework page boils down to the same five steps:

  1. Install + import the CSS once at the app entry — import "@relements/core/index.css";. From here, native elements with .re-* classes and data-* attributes work with zero JavaScript.

    <button className="re-button" type="button">
    Save
    </button>
  2. Behaviors — run enhance*(el) in the framework’s “on mount” primitive against a ref to your subtree, and call the returned controller.destroy() in “on unmount”. That is the whole lifecycle; behaviors are idempotent and remove every listener they added.

    const controller = enhanceTabs(el); // on mount
    // …
    controller.destroy(); // on unmount
  3. Events — a re-* CustomEvent is a normal bubbling DOM event. Listen with the framework’s event syntax and read event.detail (e.g. enhanceTabs emits re-change with { tabId, panelId }).

  4. Custom elements<re-*> are light-DOM custom elements, so they need no Shadow-DOM workarounds. Register each one with its bare side-effect import (import "@relements/core/elements/re-tabs";package.json lists elements/*.js under sideEffects so the customElements.define survives tree-shaking), then tell the framework to pass the unknown tag through:

    FrameworkConfig to allow <re-*> tagsListening to re-change
    React 19none — renders unknown tags as-isref + addEventListener in useEffect
    VueisCustomElement: (t) => t.startsWith("re-")@re-change="onChange"
    Svelte 5none — unknown tags pass throughonre-change={onChange} (case-sensitive)
    AngularCUSTOM_ELEMENTS_SCHEMA on the component(re-change)="onChange($event)" (cast $event)
  5. Forms / native inputs.re-input, .re-select, .re-textarea, .re-checkbox, … are just classes on native form controls, so they bind with the framework’s ordinary model/state (value/onChange, v-model, bind:value, [(ngModel)]) with no special handling.

That is it. The DOM, class names, --re-* tokens, and event contract are identical across stacks; only the glue in steps 2 and 4 changes.

Each page mirrors a runnable example app and walks through the five steps above:

Open any example live, no install — in StackBlitz or CodeSandbox (each framework page has its own one-click buttons).

Not using a framework? The same markup is the baseline everywhere — see plain HTML usage, where <re-*> elements work as-is and you listen with addEventListener("re-change", …).

Every framework page is backed by an app under docs/examples/frameworks/<fw>/ that consumes only the published @relements/core API. All five render the same one flow:

  • a <button class="re-button"> — the CSS class surface;
  • a tabs region enhanced by enhanceTabs() — the behavior surface, with its re-change event and destroy() teardown (the apps mount/unmount the region to prove cleanup);
  • a <re-tabs> custom element whose re-change event updates an <output> — the custom-element + event surface.

The DOM, class names, --re-* tokens, and re-change contract are identical across all five; only the framework glue differs. Browse the source on GitHub.