Vue
mountly-vue is the Vue 3 adapter. Same surface as the React adapter — createWidget(Component, { styles }) — different rendering layer underneath.
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 already scopes the styles per component, so collisions are rare. Pass shadow: true if you need a hard boundary (CMS embeds, third-party hosts) — 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 just 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 — 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 automatically 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 });