Skip to content

Custom element

<mountly-feature> is declarative markup for a host that composes several independently-deployed verticals. The host owns the import map and the vertical list; the element handles attach, mount, prop updates and unmount on disconnect.

<mountly-feature
module-id="signup-card"
trigger="viewport"
props='{"plan":"pro"}'
></mountly-feature>
<script type="module">
import { defineMountlyFeature } from "mountly/elements";
defineMountlyFeature({
modules: {
"signup-card": "/widgets/signup-card.js",
"payment-breakdown": "/widgets/payment-breakdown.js",
},
});
</script>

Most hosts generate that list from a manifest rather than writing it by hand, with defineMountlyFeatureFromManifest, which is the intended entry point. See microfrontends for the whole picture.

aliases maps a browser tag to a module ID, and prefix namespaces the generated ones. Custom element names must contain a hyphen, so a module ID like signup needs an alias:

defineMountlyFeature({
modules: { signup: "/widgets/signup.js" },
aliases: { "signup-card": "signup" },
prefix: "acme", // <acme-signup-card>
});

The lower-level registerCustomElement() is there when a vertical needs custom loading, data or analytics wiring.

Attribute Type Default Notes
module-id string inferred on alias tags Must match a registered feature. Alias tags use their tag name.
trigger "hover" · "click" · "focus" · "viewport" · "idle" · "media" · "url-change" "click" High-level trigger preset.
preload-on "hover" · "viewport" · "idle" · "media" none Opt-in prefetch. Never implied by trigger.
activate-on "click" · "hover" · "focus" · "viewport" · "idle" · "media" · "url-change" trigger Activation trigger when it differs from trigger.
preload-media-query string Required with preload-on="media".
activate-media-query string Required with activate-on="media" or trigger="media".
idle-timeout number ms Used by idle triggers.
viewport-root-margin string "0px" Forwarded to IntersectionObserver.
data-url string If set, the element fetches JSON from this URL as loadData.
data-method "GET" · "POST" etc "GET" Used with data-url.
props JSON string {} Passed to render(). Live-updates via update().
mount-selector CSS selector own [data-mountly-mount] slot Render target. Must match a descendant of the element.

Custom element alias tags must contain a hyphen because the browser requires it. If your internal module ID is signup, use <mountly-feature module-id="signup"> or map a valid alias:

defineMountlyFeature({
modules: { signup: "/widgets/signup.js" },
aliases: { "signup-card": "signup" },
});

Change the props attribute and the element calls feature.update(...). If the widget exposes update(container, props) (React adapter does, others can opt in), framework-internal state is preserved. Otherwise it falls back to a re-render in the same container.

const el = document.querySelector("mountly-feature");
el.setAttribute("props", JSON.stringify({ plan: "enterprise" }));

The element renders into a <div data-mountly-mount> it appends to itself. Pass mount-selector to render into a container you control instead. It has to be a descendant of the element, so the slot stays inside the element’s own subtree:

<mountly-feature module-id="payment-breakdown" trigger="click" mount-selector=".panel">
<button>Buy</button>
<div class="panel"></div>
</mountly-feature>

A selector matching nothing inside the element falls back to the default slot. To mount into a container elsewhere on the page, use the core’s data-target, which does fall back to a document-wide lookup.

If the element connects with a module-id that has no registered factory, you get a console warning listing the registered IDs. The element stays inert until you register the factory later, which helps when the registration is split across code-split chunks.

  • You need onMount / onError callbacks.
  • The trigger element is dynamic (e.g. created in response to user input).
  • You want to wire analytics next to the trigger.

In those cases, call feature.attach({ trigger, mount, … }) directly. The custom element is the ergonomic shortcut for static-HTML hosts; attach() is the full API.

You can replicate Astro timing directives in declarative HTML:

  • client:idletrigger="idle" (optionally idle-timeout)
  • client:visible={{ rootMargin }}trigger="viewport" + viewport-root-margin="..."
  • client:mediatrigger="media" + activate-media-query="(query)"

Hover has no delay attribute; it uses the core’s 100 ms settle. For a different delay, use the core directly with data-on="hover:300".

Compiler/server directives remain Astro-only (client:only, server:defer, set:html, etc.).