| name | isolet-widget-isolation |
| description | Package any component into a self-contained, isolated widget with shadow DOM, scoped styles, and multi-framework support |
| triggers | ["create an isolated widget","package component as widget","shadow DOM isolation for component","self-contained widget bundle","isolet widget","embed widget on any page","build a drop-in script tag widget","isolate React component styles"] |
isolet Widget Isolation
Skill by ara.so — Daily 2026 Skills collection.
isolet-js packages any component (React, Solid, Svelte, vanilla JS, etc.) into a self-contained, isolated widget. Widgets render inside shadow DOM by default, so styles are fully scoped. Output formats include IIFE (script tag), ESM, and CommonJS.
Install
npm install isolet-js
Core API: createIsolet
import { createIsolet } from "isolet-js";
const widget = createIsolet({
name: "my-widget",
mount: myMountFn,
css: `h1 { color: red; }`,
isolation: "shadow-dom",
shadowMode: "open",
hostAttributes: { "data-widget": "true" },
zIndex: 9999,
});
widget.mount(document.body, { title: "Hello" });
widget.update({ title: "Updated" });
widget.unmount();
widget.container;
widget.shadowRoot;
widget.mounted;
Framework Adapters
React
import { createIsolet } from "isolet-js";
import { react } from "isolet-js/react";
function Greeting({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>;
}
const widget = createIsolet({
name: "greeting",
mount: react(Greeting),
css: `h1 { color: tomato; font-family: sans-serif; }`,
});
widget.mount(document.body, { name: "World" });
widget.update({ name: "Isolet" });
widget.unmount();
Vanilla JS
import { createIsolet } from "isolet-js";
import { vanilla } from "isolet-js/vanilla";
const widget = createIsolet({
name: "counter",
mount: vanilla((container, props) => {
let count = props.initial ?? 0;
const btn = document.createElement("button");
btn.textContent = `Count: ${count}`;
btn.onclick = () => {
btn.textContent = `Count: ${++count}`;
};
container.appendChild(btn);
return () => container.removeChild(btn);
}),
});
widget.mount(document.getElementById("app"), { initial: 5 });
Solid
import { createIsolet } from "isolet-js";
import { render } from "solid-js/web";
import App from "./App";
const widget = createIsolet({
name: "solid-widget",
mount(container, props) {
const dispose = render(() => <App {...props} />, container);
return dispose;
},
});
Svelte
import { createIsolet } from "isolet-js";
import App from "./App.svelte";
const widget = createIsolet({
name: "svelte-widget",
mount(container, props) {
const app = new App({ target: container, props });
return () => app.$destroy();
},
});
Isolation Modes
createIsolet({ name: "w", mount: fn, isolation: "shadow-dom" });
createIsolet({ name: "w", mount: fn, isolation: "scoped" });
createIsolet({ name: "w", mount: fn, isolation: "none" });
CLI
npx isolet-js init
npx isolet-js build
npx isolet-js build --watch
npx isolet-js build --minify
Config File
import { defineConfig } from "isolet-js";
export default defineConfig({
name: "my-widget",
entry: "./src/index.ts",
styles: "./src/widget.css",
format: ["iife", "esm"],
outDir: "./dist",
globalName: "MyWidget",
external: ["react"],
dts: true,
minify: true,
platform: "browser",
});
Multiple Widgets
export default defineConfig([
{ name: "widget-a", entry: "./src/a.ts", styles: "./src/a.css" },
{ name: "widget-b", entry: "./src/b.ts", format: ["esm"] },
]);
CSS & Asset Handling
The build pipeline handles everything automatically:
styles in config → CSS is read, url() references (fonts, images) inlined as data URIs, result available as __ISOLET_CSS__ in your entry
.css imports → converted to JS string exports (shadow DOM safe)
- Asset imports (
.png, .woff2, .mp3, etc.) → inlined as data URIs
styles: "./path.css" in createIsolet → resolved and inlined at build time
import { createIsolet } from "isolet-js";
import { react } from "isolet-js/react";
import MyComponent from "./MyComponent";
declare const __ISOLET_CSS__: string;
export const widget = createIsolet({
name: "my-widget",
css: __ISOLET_CSS__,
mount: react(MyComponent),
});
Or reference the CSS path directly (auto-resolved at build time):
createIsolet({
name: "my-widget",
styles: "./widget.css",
mount: react(MyComponent),
});
Manual Vite Plugin Setup
If using Vite directly instead of the CLI:
import { defineConfig } from "vite";
import {
cssTextPlugin,
inlineAssetsPlugin,
autoStylesPlugin,
} from "isolet-js/plugins";
export default defineConfig({
plugins: [cssTextPlugin(), inlineAssetsPlugin(), autoStylesPlugin()],
});
Script Tag (IIFE) Usage
<script src="https://unpkg.com/isolet-js/dist/index.iife.js"></script>
<script>
const { createIsolet } = __ISOLET__;
const widget = createIsolet({
name: "inline-widget",
mount(container, props) {
container.innerHTML = `<p>Hello, ${props.name ?? "World"}!</p>`;
},
css: `p { font-family: sans-serif; color: navy; }`,
});
widget.mount(document.body, { name: "Visitor" });
</script>
For a bundled custom widget via IIFE:
export default defineConfig({
name: "my-widget",
entry: "./src/index.ts",
format: ["iife"],
globalName: "MyWidget",
minify: true,
});
<script src="./dist/my-widget.iife.js"></script>
<script>
MyWidget.widget.mount(document.getElementById("root"), { title: "Hi" });
</script>
Common Patterns
Lazy-mount on demand
const widget = createIsolet({ name: "chat", mount: react(ChatApp), css: styles });
document.getElementById("open-chat").addEventListener("click", () => {
if (!widget.mounted) {
widget.mount(document.body, { userId: currentUserId });
}
});
document.getElementById("close-chat").addEventListener("click", () => {
widget.unmount();
});
z-index overlay widget
const modal = createIsolet({
name: "modal",
mount: react(ModalComponent),
css: modalStyles,
zIndex: 10000,
hostAttributes: { role: "dialog", "aria-modal": "true" },
});
Reactive props updates
const widget = createIsolet({ name: "status", mount: react(StatusBar), css });
widget.mount(document.body, { status: "idle" });
widget.update({ status: "loading" });
widget.update({ status: "done" });
Troubleshooting
Styles leaking in or out
Use isolation: "shadow-dom" (default). Verify your css option or styles path is correctly set — without CSS in the shadow root, the host page styles will not apply inside.
__ISOLET_CSS__ is undefined
This variable is only injected by the isolet build CLI when styles is set in config. For manual Vite builds, add autoStylesPlugin() to your Vite config.
Component not rendering
Ensure the mount function appends to container, not to document.body. In shadow DOM mode, the container is inside the shadow root.
Cleanup not running
Return a cleanup function from your mount callback. Without it, widget.unmount() cannot tear down framework internals (timers, subscriptions, etc.).
IIFE global not found
Check globalName in config matches what you reference in HTML. The runtime core exposes globalThis.__ISOLET__ when no globalName is set.
External deps not found at runtime
If you set external: ["react"], the host page must provide React globally or via module federation before your widget script loads.