# How Hyperframes Handles HTML Composition Scoping: CSS and Script Isolation

> Discover how Hyperframes ensures HTML composition scoping via CSS selector rewriting and proxied IIFEs for isolated JavaScript execution. Learn more.

- Repository: [HeyGen/hyperframes](https://github.com/heygen-com/hyperframes)
- Tags: internals
- Published: 2026-05-17

---

**Hyperframes isolates each HTML composition by rewriting CSS selectors with unique attribute prefixes and wrapping JavaScript in proxied IIFEs that restrict DOM queries to the composition root.**

Hyperframes is an open-source framework developed by Heygen for building reusable HTML compositions. When rendering multiple compositions on a single page, **HTML composition scoping** prevents style leaks and script conflicts by transforming both the CSS and JavaScript at runtime.

## CSS Scoping via Selector Rewriting

The `scopeCssToComposition` function in [`packages/core/src/compiler/compositionScoping.ts`](https://github.com/heygen-com/hyperframes/blob/main/packages/core/src/compiler/compositionScoping.ts) implements CSS isolation by parsing stylesheets and prefixing every selector with a composition-specific attribute selector.

### PostCSS-Based Transformation

The function uses **PostCSS** to walk each CSS rule and rewrite selectors using `scopeSelector`. Every rule receives a prefix targeting `[data-composition-id="…"]`, ensuring styles only apply within the composition's root element.

```typescript
// packages/core/src/compiler/compositionScoping.ts
import { scopeCssToComposition } from './compiler/compositionScoping';

const originalCss = `
  .title { color: red; }
  #logo { width: 100px; }
`;
const compId = 'my-composition';
const scopedCss = scopeCssToComposition(originalCss, compId);
// => "[data-composition-id=\"my-composition\"] .title { … }"
//    "[data-composition-id=\"my-composition\"] #logo { … }"

```

### Global Rule Handling

The scoping mechanism normalizes special "root" selectors that refer to the composition container. It also removes selectors belonging to global at-rules such as `@keyframes` and `@font-face`, preserving essential global definitions while keeping element-specific rules scoped.

## JavaScript Isolation Through Proxy Objects

Script isolation is handled by `wrapScopedCompositionScript`, which encapsulates composition JavaScript in an IIFE that creates **proxy objects** for `document`, `window`, and the Hyperframes API.

### Scoped Document API

The wrapper defines a unique composition ID (`__hfCompId`) and builds a **scoped document proxy** that intercepts calls to `querySelector`, `querySelectorAll`, and `getElementById`. These calls are rewritten with `__hfNormalizeSelector` to automatically prepend the composition-specific attribute selector, then filtered using `__hfContains` to ensure they only return elements inside the composition root.

```typescript
// packages/core/src/compiler/compositionScoping.ts
import { wrapScopedCompositionScript } from './compiler/compositionScoping';

const script = `
  const title = document.querySelector('.title');
  title.textContent = 'Scoped!';
`;
const wrapped = wrapScopedCompositionScript(script, 'my-composition');
// Returns an IIFE string that creates proxies for document/window

```

### Scoped Window and Variable Management

A **scoped window proxy** redirects accesses to `__timelines` and other globals, enabling each composition to maintain its own GSAP timeline registry without collisions. The wrapper also injects per-composition variable tables (`window.__hfVariablesByComp`) before script execution, ensuring `getVariables()` returns only values belonging to the current composition.

## Runtime Loading and Assembly

The [`compositionLoader.ts`](https://github.com/heygen-com/hyperframes/blob/main/compositionLoader.ts) file orchestrates the scoping pipeline at runtime. It extracts the composition's root element, determines its `data-composition-id`, and processes each script payload through `wrapScopedCompositionScript` before appending it to the host DOM.

```typescript
// Runtime integration example
import { wrapScopedCompositionScript } from './compiler/compositionScoping';
import { scopeCssToComposition } from './compiler/compositionScoping';

async function loadComposition(html: string, compId: string) {
  // Parse HTML, extract <style> & <script> tags …
  const scopedStyle = scopeCssToComposition(styleText, compId);
  const wrappedScript = wrapScopedCompositionScript(scriptText, compId);
  // Append style & script to the host element …
}

```

The loader stores declared variables in `window.__hfVariablesByComp[compId]` **before** the wrapped script executes. The [`getVariables.ts`](https://github.com/heygen-com/hyperframes/blob/main/getVariables.ts) module exposes `window.__hyperframes.getVariables()`, which reads from this composition-specific store to provide isolated variable access.

## Summary

- **CSS Transformation**: `scopeCssToComposition` rewrites selectors with `[data-composition-id]` prefixes using PostCSS, while preserving global at-rules.
- **Script Isolation**: `wrapScopedCompositionScript` creates proxied `document` and `window` objects that automatically scope DOM queries and global accesses to the composition root.
- **Runtime Coordination**: [`compositionLoader.ts`](https://github.com/heygen-com/hyperframes/blob/main/compositionLoader.ts) injects scoped styles and wrapped scripts, managing per-composition variable stores via `window.__hfVariablesByComp`.

## Frequently Asked Questions

### What is HTML composition scoping in Hyperframes?

HTML composition scoping is the mechanism that isolates individual HTML compositions so their styles, DOM queries, and side effects cannot interfere with other compositions on the same page. It combines CSS selector rewriting with JavaScript proxying to create deterministic boundaries around each composition instance.

### How does Hyperframes prevent CSS conflicts between compositions?

According to the source code in [`packages/core/src/compiler/compositionScoping.ts`](https://github.com/heygen-com/hyperframes/blob/main/packages/core/src/compiler/compositionScoping.ts), Hyperframes prevents CSS conflicts by prefixing every selector with a unique attribute selector targeting `[data-composition-id="…"]`. This ensures style rules only match elements inside the specific composition's root element, effectively sandboxing the CSS.

### Can scripts in one Hyperframes composition access the DOM of another?

No. The `wrapScopedCompositionScript` function wraps each composition's JavaScript in an IIFE with proxied DOM APIs. The scoped document proxy intercepts `querySelector` and `querySelectorAll` calls, automatically prepending the composition-specific attribute selector and filtering results to stay within the composition root using `__hfContains`.

### How are composition-specific variables managed in Hyperframes?

Variables are stored in a global object `window.__hfVariablesByComp` using the composition ID as the key. The runtime loader populates this store before script execution, and the `getVariables()` function exposed via `window.__hyperframes` reads only from the specific composition's entry, ensuring complete isolation between variable namespaces.