--- 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](https://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 ```sh npm install isolet-js ``` ## Core API: `createIsolet` ```ts import { createIsolet } from "isolet-js"; const widget = createIsolet({ name: "my-widget", // required: unique identifier mount: myMountFn, // required: (container, props) => cleanup | void css: `h1 { color: red; }`, // optional: scoped CSS isolation: "shadow-dom", // "shadow-dom" | "scoped" | "none" shadowMode: "open", // "open" | "closed" hostAttributes: { "data-widget": "true" }, zIndex: 9999, }); widget.mount(document.body, { title: "Hello" }); // mount into target widget.update({ title: "Updated" }); // update props widget.unmount(); // tear down // Instance properties widget.container; // HTMLElement — the render container widget.shadowRoot; // ShadowRoot | null widget.mounted; // boolean ``` ## Framework Adapters ### React ```tsx import { createIsolet } from "isolet-js"; import { react } from "isolet-js/react"; function Greeting({ name }: { name: string }) { return

Hello, {name}!

; } 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 ```ts 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 cleanup function return () => container.removeChild(btn); }), }); widget.mount(document.getElementById("app"), { initial: 5 }); ``` ### Solid ```tsx 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(() => , container); return dispose; // dispose is the cleanup function }, }); ``` ### Svelte ```ts 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 ```ts // Full CSS isolation — shadow DOM (default) createIsolet({ name: "w", mount: fn, isolation: "shadow-dom" }); // Scoped — plain div wrapper, styles injected globally createIsolet({ name: "w", mount: fn, isolation: "scoped" }); // No isolation — mounts directly into target element createIsolet({ name: "w", mount: fn, isolation: "none" }); ``` ## CLI ```sh npx isolet-js init # scaffold isolet.config.ts npx isolet-js build # bundle widget(s) from config npx isolet-js build --watch # rebuild on file changes npx isolet-js build --minify # minified production build ``` ## Config File ```ts // isolet.config.ts import { defineConfig } from "isolet-js"; export default defineConfig({ name: "my-widget", entry: "./src/index.ts", styles: "./src/widget.css", // CSS to inline; url() assets become data URIs format: ["iife", "esm"], // output formats outDir: "./dist", // default: "dist" globalName: "MyWidget", // global name for IIFE builds external: ["react"], // don't bundle these deps dts: true, // emit .d.ts files minify: true, // minify output platform: "browser", // target platform }); ``` ### Multiple Widgets ```ts 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 ```ts // Entry file using __ISOLET_CSS__ injected by CLI 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__, // populated from config.styles at build time mount: react(MyComponent), }); ``` Or reference the CSS path directly (auto-resolved at build time): ```ts createIsolet({ name: "my-widget", styles: "./widget.css", // isolet build resolves this mount: react(MyComponent), }); ``` ### Manual Vite Plugin Setup If using Vite directly instead of the CLI: ```ts // vite.config.ts import { defineConfig } from "vite"; import { cssTextPlugin, inlineAssetsPlugin, autoStylesPlugin, } from "isolet-js/plugins"; export default defineConfig({ plugins: [cssTextPlugin(), inlineAssetsPlugin(), autoStylesPlugin()], }); ``` ## Script Tag (IIFE) Usage ```html ``` For a bundled custom widget via IIFE: ```ts // isolet.config.ts export default defineConfig({ name: "my-widget", entry: "./src/index.ts", format: ["iife"], globalName: "MyWidget", minify: true, }); ``` ```html ``` ## Common Patterns ### Lazy-mount on demand ```ts 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 ```ts const modal = createIsolet({ name: "modal", mount: react(ModalComponent), css: modalStyles, zIndex: 10000, hostAttributes: { role: "dialog", "aria-modal": "true" }, }); ``` ### Reactive props updates ```ts const widget = createIsolet({ name: "status", mount: react(StatusBar), css }); widget.mount(document.body, { status: "idle" }); // Later, update without remounting: 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.