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.
Signature
Section titled “Signature”The signature is intentionally identical across adapters.
function createWidget<P>( Component: ComponentType<P>, options?: AdapterOptions): WidgetModule;AdapterOptions (shared)
Section titled “AdapterOptions (shared)”| Field | Type | Notes |
|---|---|---|
styles | string | CSS 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. |
cssUrl | string | URL 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. |
moduleUrl | string | URL to the widget’s JS bundle. The adapter derives the sibling .css (/dist/index.js → /dist/index.css) unless cssUrl is also set. |
shadow | boolean | Default 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. |
reserveSize | string | CSS 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).
CSS source resolution
Section titled “CSS source resolution”When multiple sources are provided, the order from highest to lowest priority is:
cssUrlpassed viamount(container, { cssUrl })(mount-time prop).cssUrlset oncreateWidget(Component, { cssUrl })(option).moduleUrlpassed viamount(container, { moduleUrl })→ derived.css.moduleUrlset oncreateWidget(Component, { moduleUrl })→ derived.css.stylesset oncreateWidget(literal string).
If a fetch fails (404, network error), mount still succeeds. The widget renders unstyled rather than throwing.
Adapter-specific extensions
Section titled “Adapter-specific extensions”mountly-svelte
Section titled “mountly-svelte”Required when the component is Svelte 5:
| Field | Type | Notes |
|---|---|---|
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 }).
Returned WidgetModule
Section titled “Returned WidgetModule”interface WidgetModule { mount(container: HTMLElement, props: Record<string, unknown>): void; unmount(container: HTMLElement): void; // React adapter also exposes: // update?(container, props): void;}React example
Section titled “React example”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 });Vue example
Section titled “Vue example”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.
Svelte 4 example
Section titled “Svelte 4 example”import { createWidget } from 'mountly-svelte';import SignupCard from './SignupCard.svelte'; // Svelte 4 class component
export default createWidget(SignupCard);Svelte 5 example
Section titled “Svelte 5 example”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.Styling
Section titled “Styling”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:
- If a
cssUrlormoduleUrlis set (in options or props), fetch the corresponding.cssbefore render. In light DOM it is injected via the normalstylespath (shared<head>style orstyleMode: "isolated"); withshadow: trueit is adopted into the shadow root. - Otherwise, use the literal
stylesoption if one was provided. - 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.
Light DOM (default)
Section titled “Light DOM (default)”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.
Shadow DOM (opt in with shadow: true)
Section titled “Shadow DOM (opt in with shadow: true)”createWidget(Card, { shadow: true, styles });When shadow: true:
- The adapter calls
attachShadow(container, { mode: shadowMode ?? "open" }). options.stylesis adopted into the shadow root viaadoptedStyleSheets(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
containeritself 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.
Related
Section titled “Related”createOnDemandFeature— wrap the widget with on-demand lifecycle.- Styling for the full CSS resolution matrix.
- React, Vue, Svelte — adapter-specific notes.