Skip to content

Svelte

mountly-svelte is the Svelte adapter. It supports both Svelte 4 (legacy class component API) and Svelte 5 (functional component API), and auto-detects which one your component uses.

Terminal window
pnpm add mountly mountly-svelte svelte

If you’re on Svelte 4, the adapter detects the legacy class API and uses new Component({ target, props }) internally. No extra setup.

signup-card.ts
import { createWidget } from "mountly-svelte";
import SignupCard from "./SignupCard.svelte";
export default createWidget(SignupCard);

The Vite library build emits dist/index.js plus dist/index.css (Svelte’s component styles, hashed). The adapter fetches the CSS when the host wires up moduleUrl; see “On-demand lifecycle (zero-config)” below.

Svelte 5 components are plain functions. Pass mount and unmount from "svelte":

signup-card.ts
import { createWidget } from "mountly-svelte";
import { mount, unmount } from "svelte";
import SignupCard from "./SignupCard.svelte";
export default createWidget(SignupCard, { mount, unmount });

If you omit them, the adapter imports "svelte" itself. That works, at the cost of one promise hop on first mount.

import widget from "./signup-card.js";
widget.mount(document.querySelector("#cta-mount"), {
headline: "Try the API",
});

Each mount() unmounts any existing instance in the same container first.

import { createOnDemandFeature } from "mountly/feature";
const signup = createOnDemandFeature({
moduleId: "signup-card",
moduleUrl: "/widgets/signup-card/dist/index.js",
});
signup.attach({ trigger: btn, preloadOn: "hover", activateOn: "click" });

mountly threads moduleUrl through to the adapter, which fetches dist/index.css (sibling) and applies it before render. No FOUC, no manual loader. Default light DOM lets the host’s design system reach in. Pass shadow: true for hard isolation.

Need to override? loadModule / render still work and trump the auto-derived defaults.

feature.update() patches props in place, so the component instance survives and its local state with it.

  • Svelte 4: the adapter calls $set on the instance.
  • Svelte 5, adapter-loaded runtime: the adapter hands Svelte a $state-tracked props object and assigns onto it. A prop the host drops clears to undefined rather than lingering from the last render.
  • Svelte 5, your own mount/unmount: there is no tracked object to patch, so the adapter assigns onto the mount handle. That reaches the component’s exported bindings only. Omit mount/unmount if you want prop updates to flow.

Same risk as React/Vue. Use the peer build pattern for multi-widget pages: externalise svelte and provide it once via the host’s import map.

Svelte’s component-scoped styles already isolate via Svelte’s hash. With shadow: true they get an additional hard boundary. Global styles (:global(...)) inside a Svelte component land inside the shadow tree when shadow: true (so they don’t leak to the document); in default light DOM they reach the document, which is usually what you want.

To hand a Svelte component to a team that does not use Svelte, point a build at it and give it a namespace. Mountly reads let { … }: Props = $props() and publishes the component as a custom element:

vite.config.ts
import { defineElementsConfig } from "mountly-vite-plugin";
export default defineElementsConfig({ prefix: "acme", elements: "src/elements/*.svelte" });

The consuming page needs one script tag and no package install. See Script-tag component libraries.

The Vite + @sveltejs/vite-plugin-svelte library build emits a sibling dist/index.css for any component that has <style>. The adapter picks it up when the host passes moduleUrl:

import { createWidget } from "mountly-svelte";
import Component from "./Component.svelte";
export default createWidget(Component);

Compile Tailwind into the same dist/index.css (via your build’s CSS entry) and the same zero-config flow applies. If your Tailwind output lives at a non-default path, point the host at it explicitly:

createOnDemandFeature({
moduleId: "card",
moduleUrl: "/widgets/card/dist/index.js",
// No assetOptions needed; adapter derives /widgets/card/dist/index.css.
});

If you’d rather inline the CSS string at build time (e.g. small components, no shared Tailwind layer), that path still works:

import { createWidget } from "mountly-svelte";
import Component from "./Component.svelte";
import styles from "./styles.css?inline";
export default createWidget(Component, { styles });