Skip to content

mountly

v0.1 · ~9 KB gzipped core entry

Framework components. Any page. On demand.

mountly turns React, Vue, Svelte, and TSRX components into lazy, HTML-addressable features. Drop them into CMS pages, marketing sites, legacy apps, partner embeds, or framework migrations without a host-app rewrite. Light DOM is the default; use shadow: true when you need hard style isolation.

~9KBcore entry (gzipped)
7activation triggers
4adapter packages
0SPA required
0x · Runtime shape

Small core + explicit subpaths.

mountly keeps a small top-level core entry and exposes focused subpath imports (for example mountly/attach, mountly/elements, mountly/shadow, mountly/assets). In browser import-map hosts, map only the subpaths you use.

01 · Positioning

One primitive. Several high-value jobs.

The core idea is simple: a component becomes a mountable widget, then mountly loads and activates it from plain HTML when the page says it is time. Marketing reuse, strangler-fig migration, island-style UI, and embeds are all applications of the same runtime.

What you keep

  • Normal React, Vue, Svelte, or TSRX components
  • Existing CMS, static, legacy, or app host pages
  • Current routing, query-string state, and deployment model
  • Per-widget ownership by the component team

No new component authoring model. No full app rewrite.

What mountly adds

  • HTML tags like <signup-card>
  • Activation on idle, viewport, click, hover, media, or URL change
  • Shadow-DOM style isolation and automatic CSS loading
  • Module/data caching, teardown, and prop updates

A small runtime for component delivery, not an app platform.

02 · Use cases

Where the primitive pays off.

01marketingReal product componentsReuse checkout, pricing, signup, calculator, and lead-capture components on fast campaign pages.
02migrationStrangler fig UIReplace legacy surfaces one widget at a time while the existing host keeps its routing and release process.
03islandsRuntime islandsGet Astro-like timing for HTML hosts, or compose with Astro when Astro owns the shell and mountly owns a portable widget.
04embedsPartner and CMS dropsExpose a stable custom tag and let non-app teams place interactive UI without owning framework code.
05performanceIntent-paid JavaScriptKeep the page shell light, then load heavy UI when users scroll, click, hover, or match a route.
06transitionMulti-framework pagesRun React, Vue, Svelte, and TSRX widgets side by side while teams converge on the next architecture.
03 · Triggers

Six ways to say “now”.

A trigger is the signal that moves a feature from idle to preload to mount. Pick the one that matches user intent — or compose your own with a plugin.

01on intenthoverPreload on mouseenter, mount on click. The default for discoverable controls.
02explicitclickMount on click without preloading. Use when intent is committed, not exploratory.
03accessibilityfocusPreload on focus, mount on commit. Keyboard parity with hover.
04below the foldviewportMount when the element scrolls into view. Configurable threshold.
05predictiveidleUse requestIdleCallback to preload during quiet moments.
06routingurl-changeMount when the URL matches a pattern. Pairs with client-side navigation.
04 · Lifecycle

One state machine, five states. The same shape on every framework.

Every feature moves through the same sequence regardless of trigger or framework. You can hook into any phase, abort in flight, and unmount cleanly.

01idleNothing loaded. Waiting for the trigger.
02preloadModule fetched, data fetch may begin in parallel.
03activateCommitment signal. Finalise data, prepare to mount.
04mountWidget rendered into the container (light DOM by default; shadow root with shadow: true).
05unmountRemoved from DOM. Caches retained for re-mount.
05 · The whole API

A widget. A feature. A trigger. Three lines you’ll write.

Components stay components. The adapter wraps them as widgets. The host can wire a feature imperatively, or declare it directly as HTML.

signup-card.ts
import { createWidget } from 'mountly-react';
import SignupCard from './SignupCard.tsx';
import styles from './SignupCard.css?inline';
// 1. Wrap a component as a framework-agnostic widget
export default createWidget(SignupCard, { styles });
page.ts
import { createOnDemandFeature } from 'mountly';
// 2. Add the on-demand lifecycle around a widget
const signup = createOnDemandFeature({
moduleId: 'signup-card',
loadModule: () => import('./signup-card.js'),
render: ({ mod, container, props }) => mod.mount(container, props),
});
// 3. Attach it to a DOM trigger — preload on hover, mount on click
signup.attach({
trigger: document.querySelector('#cta')!,
preloadOn: 'hover',
activateOn: 'click',
});

Styling — CSS is auto-loaded with the module and applied before render. No FOUC. Default light DOM lets the host’s design system reach in; pass shadow: true for full isolation.

host.html
<signup-card trigger="viewport" props='{"plan":"pro"}'></signup-card>
<script type="module">
import { defineMountlyFeature } from 'mountly';
defineMountlyFeature('/widgets/dist/index.js');
</script>
06 · How it compares

Composable with frameworks. Useful where framework boundaries end.

ApproachBest atTradeoffComplexity
Traditional SPAOne app owns every surfacePoor fit for CMS, legacy, or partner-hosted UILow
Framework code-splittingRoutes inside a single framework appDoes not standardize HTML drops or host-agnostic lifecycleMedium
Microfrontend orchestratorsIndependent apps with org-level ownershipOperationally heavy for component-sized featuresHigh
mountlyComponent features in any HTML pageNot a router, SSR framework, or control planeLow
07 · Packages

One runtime, four adapters, one optional design preset.

mountly · Apache-2.0 · pre-1.0

github.com/jagreehal/mountly

see ./examples in the repo