isolet: The Widget Runtime That Treats the Host Page Like a Hazard Zone

A deep dive into how Shadow DOM, CSP-aware style injection, and persistent framework adapters let one component ship safely across unknown websites.

9 min read · millionco/isolet

A lone widget capsule lands inside a hostile desktop of tangled CSS selectors and browser fragments, while a hard shell holds the widget intact. The image explains the core problem: third-party UI has to survive outside its own clean environment.
`isolet` is built for the messy edge where your component meets somebody else’s page.
Key Takeaways

When the host page becomes the enemy

Third-party widgets usually fail in boring ways. A global CSS rule leaks in, a host page blocks a style tag, or a framework gets rebuilt because the embed has no stable lifecycle. `isolet` is interesting because it treats those failures as the default case, not the edge case.

That changes the design brief. The goal is not just to render a component on someone else’s page. The goal is to ship UI into unknown territory and still trust the result.

A close-up pipeline shows separate inputs like a stylesheet and a framework component entering an isolation chamber, then emerging as a self-contained deployable widget bundle. The image explains how `isolet` turns ordinary app code into something ready to mount safely in an unknown host page.
The build step is part packaging, part containment.

What isolet actually ships

The repo is a pnpm monorepo with a small, purposeful shape: a core runtime, a CLI, and a demo app. That matters because it tells you `isolet` is not just a component library. It is a delivery system for isolated widgets.

The full system is a bridge between a messy host page and a stable framework adapter.

The architecture is cleaner than the marketing language suggests. The host page provides a target node. `isolet` creates an isolated shell. The framework adapter keeps the actual widget identity alive across updates.

The isolation stack

The core move lives in `mount-container.ts`, where the runtime chooses among three modes: `shadow-dom`, `scoped`, and `none`. Shadow DOM is the default, which is the right choice if your first concern is style leakage.

The important detail is not just that a shadow root exists. It is that the host is hardened with a reset like `all: initial`, and mount operations are idempotent through a `data-isolet` marker. That means the runtime is trying to be both defensive and reusable.

ConcernPlain script embedisoletiframe
Style leakageHigh riskLow in shadow-dom modeVery low
CSP compatibilityOften fragileNonce-aware injectionUsually strong
Host integrationDirect but brittleNative-feelingSeparated document
Performance overheadLowLow to moderateHigher
Framework reuseDepends on hostPreserved through adaptersHarder across the boundary
Isolation strengthWeakStrong enough for many widgetsStrongest

This is the middle ground that makes `isolet` worth studying. It does not try to out-iframe the iframe. It tries to remove the iframe tax when the workload does not need a second document.

Why updates do not tear the widget down

`create-isolet.ts` gives the runtime a simple lifecycle: mount, update, unmount. Under the hood, that simplicity is doing real work. State is tracked in a closure, props are shallow-merged, and the adapter can re-render without reconstructing the whole widget shell.

const widget = createIsolet({
  mount(target, props) {
    // adapter renders into a persistent root
  },
});

widget.mount(root, { theme: 'light' });
widget.update({ locale: 'en' });
widget.unmount();

That persistence matters more than it sounds. If the root survives, framework internals survive with it. React can diff. Vanilla code can keep the same container. The widget feels stable because it actually is stable.

CSS is part of the bundle, not an afterthought

`isolet` treats CSS as something to package, not something to hope for. The build path rewrites CSS into text, then injects it into the isolated container rather than depending on linked stylesheets to behave on an unknown site.

That is where `detectCspNonce.ts` matters. A host page with a strict Content Security Policy can break a naive embed instantly. If the runtime can detect a nonce and attach styles correctly, it becomes viable in environments that usually reject widget experiments outright.

This is one of those details that looks small until you ship to a real customer. CSP is not a niche concern once the audience includes enterprise users.

The React adapter is more conservative than it looks

The React adapter is not trying to hide React. It is trying to preserve React. That means a persistent root, controlled re-renders, and a clean bridge between `update()` on the wrapper and the framework’s own diffing logic.

// conceptual shape
const root = createRoot(container);
root.render(<Widget {...props} />);

return {
  update(nextProps) {
    root.render(<Widget {...merge(props, nextProps)} />);
  }
};

That restraint is a design strength. The adapter does less than a generic abstraction layer would do, which is why it can do the important thing well: keep the widget alive without making the framework feel alien.

Why this beats an iframe, and when it does not

`isolet` is not a universal replacement for iframes. If you need the strongest possible boundary, a separate document still wins. But if you want the ergonomics of a native component with much of the safety of isolation, this is a compelling trade.

OptionBest atWeakest atWhen to choose it
Plain script embedLowest frictionSafety and containmentInternal surfaces, trusted hosts
isoletSafe-enough isolation with native feelHard boundary guaranteesEmbeds that need reuse, style safety, and framework adapters
iframeMaximum isolationIntegration and overheadUntrusted content, strict separation, legacy-safe sandboxes

That is the real thesis. `isolet` is not about proving iframes obsolete. It is about making widget shipping feel less like a compromise and more like an engineering choice.