Vue
mountly-vue is the Vue 3 adapter. It exposes the same surface as the React adapter, createWidget(Component, { styles }), over a different rendering layer.
Install
Section titled “Install”pnpm add mountly mountly-vue vueBasic usage
Section titled “Basic usage”import { createWidget } from "mountly-vue";import SignupCard from "./SignupCard.vue";
export default createWidget(SignupCard);By default the widget mounts in light DOM. The adapter fetches the sibling dist/index.css (extracted by Vite’s Vue plugin from <style scoped>) and applies it before render. Vue’s data-v-… hash scopes the styles per component, so collisions are rare. Pass shadow: true if you need a hard boundary, such as CMS embeds or third-party hosts. The styles are then scoped twice, once by Vue and once by the shadow root.
Want to inline the CSS string instead? That’s still fine:
import styles from "./SignupCard.css?inline";export default createWidget(SignupCard, { styles });Mounting
Section titled “Mounting”import widget from "./signup-card.js";
widget.mount(document.querySelector("#cta-mount"), { headline: "Try the API", plan: "pro",});Each mount() unmounts any existing Vue app in the same container first.
Under the hood, the adapter calls createApp({ render: () => h(Component, props) }) and mounts that into the container (shadow root if shadow: true, otherwise light DOM). There is one Vue app per container.
On-demand lifecycle (zero-config)
Section titled “On-demand lifecycle (zero-config)”import { createOnDemandFeature } from "mountly";
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. Use loadModule / render only when you need bespoke behaviour.
Composition API and Options API
Section titled “Composition API and Options API”Both work. The adapter doesn’t care which API style your component uses. It calls createApp with the imported component.
Provide / inject
Section titled “Provide / inject”Each widget mount is its own Vue app. Providers do not cross widgets. If two widgets need to share state, either:
- Wrap them in one outer Vue app and use
mountlyonly for the load lifecycle (you mount the outer app yourself). - Use a framework-agnostic store (Pinia with a global instance, or any
import-level singleton).
Two builds, one source
Section titled “Two builds, one source”mountly init --vue (when supported by the CLI version you have) configures tsup to emit:
dist/index.js: bundles Vue.dist/peer.js: externalises Vue. Pairs with an import map.
Multi-widget hosts should use the peer build to share one Vue copy. See Distribution.
Common pitfalls
Section titled “Common pitfalls”Style leakage
Section titled “Style leakage”Vue SFC <style scoped> works inside the widget. With shadow: true the shadow root adds another layer of isolation; without it, Vue’s data-v-… hash is usually enough. If you’re using global utility classes (Tailwind), those go into styles (or a host <link>) so the widget can reach them.
Two Vue copies
Section titled “Two Vue copies”Same hazard as React: two widgets with dist/index.js give you two Vue runtimes on the same page. Use the peer build for multi-widget pages.
Single-File Components
Section titled “Single-File Components”Vite’s Vue plugin handles .vue files in the widget build. If you’re running tsup directly, install unplugin-vue or a similar SFC compiler, since mountly-vue does not ship one.
Reference
Section titled “Reference”createWidget: the adapter API.createOnDemandFeature: the lifecycle wrapper.
Tailwind and non-Tailwind defaults
Section titled “Tailwind and non-Tailwind defaults”No Tailwind
Section titled “No Tailwind”import { createWidget } from "mountly-vue";import Component from "./Component.vue";
export default createWidget(Component);The Vite Vue plugin extracts <style scoped> blocks into dist/index.css. The adapter picks it up when the host passes moduleUrl.
With Tailwind
Section titled “With Tailwind”Compile Tailwind into the same dist/index.css and the zero-config flow still applies. If you prefer to ship the CSS as an inline string:
import { createWidget } from "mountly-vue";import Component from "./Component.vue";import styles from "./styles.css?inline";
export default createWidget(Component, { styles });