Trending-skills isolet-widget-isolation
Package any component into a self-contained, isolated widget with shadow DOM, scoped styles, and multi-framework support
install
source · Clone the upstream repo
git clone https://github.com/Aradotso/trending-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Aradotso/trending-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/isolet-widget-isolation" ~/.claude/skills/aradotso-trending-skills-isolet-widget-isolation && rm -rf "$T"
manifest:
skills/isolet-widget-isolation/SKILL.mdsource content
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
createIsoletimport { 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
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 cleanup function 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; // dispose is the cleanup function }, });
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
// 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
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
// 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
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:
in config → CSS is read,styles
references (fonts, images) inlined as data URIs, result available asurl()
in your entry__ISOLET_CSS__
imports → converted to JS string exports (shadow DOM safe).css- Asset imports (
,.png
,.woff2
, etc.) → inlined as data URIs.mp3
instyles: "./path.css"
→ resolved and inlined at build timecreateIsolet
// 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):
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:
// 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
<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:
// isolet.config.ts export default defineConfig({ name: "my-widget", entry: "./src/index.ts", format: ["iife"], globalName: "MyWidget", minify: true, });
<!-- Resulting script tag distribution --> <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" }); // 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.
is undefined
This variable is only injected by the __ISOLET_CSS__
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.