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.
In one example
Section titled “In one example”<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.
Alias tags
Section titled “Alias tags”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.
Attributes
Section titled “Attributes”| 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" },});Live prop updates
Section titled “Live prop updates”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" }));Mount target
Section titled “Mount target”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.
Unknown module-id
Section titled “Unknown module-id”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.
When to prefer the imperative API
Section titled “When to prefer the imperative API”- You need
onMount/onErrorcallbacks. - 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.
Astro parity scope
Section titled “Astro parity scope”You can replicate Astro timing directives in declarative HTML:
client:idle→trigger="idle"(optionallyidle-timeout)client:visible={{ rootMargin }}→trigger="viewport"+viewport-root-margin="..."client:media→trigger="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.).