Overview
A handful of patterns ship as <re-*> custom elements — light-DOM web
components that wrap an enhance* behavior
in a self-managing tag. Drop the tag into any template and it enhances its own
markup on connect and cleans up on disconnect, with no init call to remember.
They are a convenience layer, not a new contract: the inner markup is the same
semantic HTML with the same .re-* classes, so an element and its behavior are
interchangeable. There are four of them — everything else is
behavior-only.
Register, then use
Section titled “Register, then use”Each element is a side-effect import that registers the tag with the browser. Import it once, anywhere in your bundle — the registration is global and guarded, so importing twice is a no-op:
// Registers <re-tabs> for the whole page.import "@relements/core/elements/re-tabs";After that the tag works declaratively in plain HTML or any framework template — no per-instance wiring:
<re-tabs aria-label="Account"> <div role="tablist"> <button role="tab" id="t-1" aria-controls="p-1" aria-selected="true">Profile</button> <button role="tab" id="t-2" aria-controls="p-2" aria-selected="false" tabindex="-1"> Security </button> </div> <section role="tabpanel" id="p-1" aria-labelledby="t-1">…</section> <section role="tabpanel" id="p-2" aria-labelledby="t-2" hidden>…</section></re-tabs>Each element exposes a small imperative API (a property or a method) and emits
the same bubbling re-* CustomEvents as its behavior, so any framework can
read state and listen with plain addEventListener:
const tabs = document.querySelector("re-tabs");tabs.value = "t-2"; // select the Security tabtabs.addEventListener("re-change", (e) => console.log(e.detail)); // { tabId, panelId }Registration is not part of the root @relements/core import — pull in each
element’s subpath so you only ship the tags you use.
The four elements
Section titled “The four elements”| Element | Wraps | JS API | Emits |
|---|---|---|---|
<re-tabs> | enhanceTabs | element.value — read/write the selected tab id | re-change ({ tabId, panelId }) |
<re-menu> | enhanceMenuButton | element.open — reflect/control the open state | re-select ({ item, value }) |
<re-popover> | enhancePopover | .show() / .hide() / .toggle() — native popover API | re-toggle |
<re-toast> | showToast | .show(message, options) — returns the toast controller | — |
Custom element or behavior?
Section titled “Custom element or behavior?”Both wire the same logic — pick by integration style:
- Reach for a custom element when you want a declarative, self-managing tag.
It enhances and cleans itself up via
connectedCallback/disconnectedCallback, so there is no lifecycle to manage. This is the natural fit for plain HTML pages and for frameworks that render custom elements directly. - Reach for the
enhance*behavior when you want to control the lifecycle yourself — a global page init, a framework component enhancing its own subtree on mount and callingdestroy()on unmount, or scoping enhancement to aShadowRoot. Behaviors are also the only option for patterns with no element wrapper.
Because the underlying markup is identical, you can start with one and switch
later without touching the document’s structure. The
behaviors & custom elements guide
covers the full trade-off and the enhance* contract in depth.