Skip to content

createWidget

import { createWidget } from 'mountly-react'; // or "mountly-vue" / "mountly-svelte"

Turns a component into a framework-agnostic widget, an object with mount(container, props) and unmount(container) that mountly’s runtime can drive.

The signature is intentionally identical across adapters.

function createWidget<P>(
Component: ComponentType<P>,
options?: AdapterOptions
): WidgetModule;
FieldTypeNotes
stylesstringCSS string. In light DOM (default) it is injected once as a <style data-mountly-fallback> in <head> (or inline next to the mount with styleMode:"isolated"). In shadow DOM it is adopted into the shadow root.
cssUrlstringURL to fetch CSS from at mount time. Sent with Accept: text/css so Vite-style dev servers return raw CSS instead of an HMR-wrapped JS module.
moduleUrlstringURL to the widget’s JS bundle. The adapter derives the sibling .css (/dist/index.js/dist/index.css) unless cssUrl is also set.
shadowbooleanDefault false (light DOM). Set shadow: true to mount inside a shadow root, which scopes the widget’s styles and stops host CSS from reaching in.
shadowMode"open" · "closed"Only used when shadow: true. Default "open".
styleMode"shared" · "isolated"Light-DOM only. "shared" (default) appends a single <style> to <head>. "isolated" writes a <style> next to the mount point.
reserveSizestringCSS applied to the container before render. Useful for preventing CLS while the widget loads.

cssUrl, moduleUrl, and shadow: true apply to all three adapters (mountly-react, mountly-vue, mountly-svelte).

When multiple sources are provided, the order from highest to lowest priority is:

  1. cssUrl passed via mount(container, { cssUrl }) (mount-time prop).
  2. cssUrl set on createWidget(Component, { cssUrl }) (option).
  3. moduleUrl passed via mount(container, { moduleUrl }) → derived .css.
  4. moduleUrl set on createWidget(Component, { moduleUrl }) → derived .css.
  5. styles set on createWidget (literal string).

If a fetch fails (404, network error), mount still succeeds. The widget renders unstyled rather than throwing.

Required when the component is Svelte 5:

FieldTypeNotes
mount<P>(C, opts) => Record<string, unknown>Pass mount from "svelte".
unmount(handle) => void | Promise<void>Pass unmount from "svelte".

Svelte 4 components need neither — the adapter detects the legacy class and uses new Component({ target, props }).

interface WidgetModule {
mount(container: HTMLElement, props: Record<string, unknown>): void;
unmount(container: HTMLElement): void;
// React adapter also exposes:
// update?(container, props): void;
}
import { createWidget } from 'mountly-react';
import SignupCard from './SignupCard.tsx';
// Lowest-ceremony: emit a sibling .css next to dist/index.js (Vite library
// mode does this by default for components with stylesheets) and let
// createOnDemandFeature thread `moduleUrl` through to the adapter.
export default createWidget(SignupCard);

The React adapter exposes update() automatically. feature.update(container, props) reuses the same React root, preserving hooks state. CSS Modules pair naturally with the default light-DOM mount: the build emits hashed class names referenced from JSX, and the host’s design system can still reach in. If you want full isolation, add shadow: true. See Styling.

If you’d rather inline the CSS at build time:

import styles from './SignupCard.css?inline';
export default createWidget(SignupCard, { styles });
import { createWidget } from 'mountly-vue';
import SignupCard from './SignupCard.vue';
export default createWidget(SignupCard);

One Vue app per container. Two widget mounts = two Vue apps; providers don’t cross.

import { createWidget } from 'mountly-svelte';
import SignupCard from './SignupCard.svelte'; // Svelte 4 class component
export default createWidget(SignupCard);
import { createWidget } from 'mountly-svelte';
import { mount, unmount } from 'svelte';
import SignupCard from './SignupCard.svelte'; // Svelte 5 functional component
export default createWidget(SignupCard, { mount, unmount });

The adapter auto-loads mount / unmount from "svelte" if you don’t supply them, but passing them explicitly avoids a deferred dynamic import on first mount.

If you forget mount / unmount on a v5 component, you’ll see:

[mountly-svelte] Svelte 5 component detected but `mount`/`unmount` options were not provided.

No manual CSS loading, no FOUC, no host conflicts. Every widget gets automatic companion CSS loading; it arrives before the widget renders.

The adapter follows this resolution order on each mount:

  1. If a cssUrl or moduleUrl is set (in options or props), fetch the corresponding .css before render. In light DOM it is injected via the normal styles path (shared <head> style or styleMode: "isolated"); with shadow: true it is adopted into the shadow root.
  2. Otherwise, use the literal styles option if one was provided.
  3. Otherwise, render unstyled. That’s fine when the host page already wires a global stylesheet.

Fetched CSS is cached in-memory per URL, so repeated mounts of the same widget never refetch. See Styling for the full matrix including light DOM, CSS Modules, and noscript fallback patterns.

By default createWidget mounts in light DOM. The widget renders directly inside the container, the host’s CSS reaches in, and the widget’s styles are injected as a single <style data-mountly-fallback> in <head> (or inline next to the mount with styleMode: "isolated"). This is the right default for most product code, where you want your design system to apply naturally.

createWidget(Card, { shadow: true, styles });

When shadow: true:

  • The adapter calls attachShadow(container, { mode: shadowMode ?? "open" }).
  • options.styles is adopted into the shadow root via adoptedStyleSheets (shared across all roots that use the same CSS string).
  • The host’s CSS doesn’t bleed in, modulo CSS custom properties which inherit through shadow roots intentionally.
  • The container itself becomes the shadow host; the widget renders into the shadow tree.

Reach for shadow DOM when the widget will be embedded in unknown hosts (CMS pages, third-party sites, legacy apps) and must look the same regardless of host CSS.

If the container is a void element like <img> or <input>, attachShadow throws. The adapter falls back to light DOM in that case and logs a warning.