On-Demand Interactive UI Platform
Load rich UI only when the user actually needs it. Use your existing React, Vue, or Svelte components with no new component model. Modernize legacy pages incrementally without rewriting the host app.
Documentation: https://jagreehal.github.io/mountly
Modern web apps ship too much JavaScript upfront. Component libraries load everything at once. Microfrontends are operationally heavy. Framework lazy-loading lacks standardized interaction patterns.
There's no unified system for: "Load rich UI only when the user actually needs it."
mountly has two layers and uses one term for each — they are not synonyms.
createWidget(Component, { styles }) which returns { mount, unmount }. A Widget knows nothing about how or when it's loaded.createOnDemandFeature(...) or declared with <mountly-feature>.Rule of thumb: you write Components, the adapter wraps them as Widgets, and you ship them as Features. Don't substitute the terms in code, docs, or examples — Component is the input, Widget is the framework-agnostic output, Feature adds the lazy-load lifecycle on top.
mountly is a frontend platform for building Features — Widgets that load only on user intent (hover, click, focus, viewport entry, or idle time).
Before mountly: After mountly:
┌──────────────────────┐ ┌──────────────────────┐
│ Full app JS bundle │ │ Page shell (light) │
│ - Payment widget │ │ │
│ - Video player │────► │ [User hovers] │
│ - Image lightbox │ │ → Load widget code │
│ - Analytics panel │ │ → Fetch data │
│ - Chat widget │ │ → Mount UI │
└──────────────────────┘ └──────────────────────┘
Slow TTI, heavy bundle Fast TTI, lean bundle
idle → preload → activate → mount → unmount<mountly-feature> web component for declarative usage| Package | Purpose |
|---|---|
mountly |
Core runtime, on-demand loader, lifecycle, custom element, CLI |
mountly-react |
React adapter — createWidget(Component, { styles }) |
mountly-vue |
Vue adapter — createWidget(Component, { styles }) |
mountly-svelte |
Svelte adapter — createWidget(Component, { styles }) |
mountly-tailwind |
Tailwind v4 design preset (opt-in) |
npx mountly init my-widget
cd my-widget
pnpm install
pnpm build
Drop the built widget into any HTML page:
<div id="mount"></div>
<script type="module">
import widget from "./my-widget/dist/index.js";
widget.mount(document.getElementById("mount"));
</script>
The widget mounts inside its own shadow root with bundled styles. That's the whole flow.
See it running first: clone the repo, run pnpm install && pnpm -r build && cd examples/plain-html && pnpm dev, then open http://localhost:5175/examples/quickstart/host.html (source).
createOnDemandFeature(...) adds hover/click/viewport/idle triggers around a widget. See examples/marketing-site.installRuntime({...}) injects a shared-React import map. See examples/plain-html.mountly is pre-1.0, but the public API used in the examples is now frozen for the 0.1.x line:
createOnDemandFeatureregisterCustomElement / defineMountlyFeatureWidgetModule, AdapterOptions)installRuntime shape (including react/jsx-runtime mapping support)Breaking changes to this surface should wait for 0.2.0 and must be called out in release notes. Releases follow docs/release-checklist.md.
See examples/README.md for start order, ports, and when to use each pattern.
Summary:
examples/payment-breakdown — a popover with async data loading and shadow-DOM stylingexamples/image-lightbox — a media viewer with focus restorationexamples/signup-card — a marketing cardexamples/demo — a Vite host that exercises all of the aboveexamples/plain-html — bundler-free integration via import mapsexamples/marketing-site — embedding widgets in static HTMLexamples/quickstart/host.html — minimal import map + attach() hostexamples/pokemon-kitchen-sink — stress-test of all featurespnpm install
pnpm -r build
pnpm test
MIT