Frontend Development 11 min read

Implementing a CSS Sandbox for Qiankun: Shadow DOM and Scoped CSS Isolation

This article explains how to build a CSS sandbox for Qiankun micro‑frontends by using Shadow DOM for strict style isolation and a Scoped CSS approach for experimental isolation, providing step‑by‑step code examples, underlying principles, and a final Web Component implementation.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Implementing a CSS Sandbox for Qiankun: Shadow DOM and Scoped CSS Isolation

The article begins with a brief introduction, stating that it will demonstrate how to create a CSS sandbox for Qiankun, complementing a previous tutorial on a JavaScript sandbox.

Preparation

Three files are required: index.html (the entry HTML), shadowDOMIsolation.js (Shadow DOM sandbox implementation), and scopedCSSIsolation.js (Scoped CSS sandbox implementation). The source code is hosted in the mini-css-sandbox repository.

Shadow DOM Sandbox

Principle

Shadow DOM attaches a hidden, independent DOM tree to a regular DOM element (the shadow host), providing hard style isolation.

Implementation

The shadowDOMIsolation function trims the HTML string, creates a container div , extracts the root element, clears its content, attaches a shadow root (using attachShadow({mode:'open'}) or the legacy createShadowRoot ), injects the original inner HTML into the shadow root, and returns the element.

function shadowDOMIsolation(contentHtmlString) {
  // Clean HTML
  contentHtmlString = contentHtmlString.trim();

  // Create container div
  const containerElement = document.createElement('div');
  containerElement.innerHTML = contentHtmlString;

  // Get root div element
  const appElement = containerElement.firstChild;

  const { innerHTML } = appElement;
  appElement.innerHTML = '';

  let shadow;
  if (appElement.attachShadow) {
    // Modern API
    shadow = appElement.attachShadow({ mode: 'open' });
  } else {
    // Legacy API
    shadow = appElement.createShadowRoot();
  }

  // Populate shadow DOM
  shadow.innerHTML = innerHTML;

  return appElement;
}

The resulting effect shows that external styles (e.g., a global red p style) do not affect the content inside the shadow DOM.

Scoped CSS Sandbox

Principle

Scoped CSS extracts the <style> text from a micro‑app and rewrites each selector by prefixing it with a unique container selector such as div[data-app-name="MyApp"] , ensuring the styles apply only within that container.

Implementation

The core functions are:

processCSS(appElement, stylesheetElement, appName) – creates a temporary disabled <style> element to obtain CSS rules, then rewrites them using rewrite .

ruleStyle(rule, prefix) – replaces the selector part of a CSS rule with the prefixed selector.

rewrite(rules, prefix) – iterates over CSS rules, handling STYLE rules (others like MEDIA and SUPPORTS are omitted for brevity).

function processCSS(appElement, stylesheetElement, appName) {
  const prefix = `${appElement.tagName.toLowerCase()}[data-app-name="${appName}"]`;
  const tempNode = document.createElement('style');
  document.body.appendChild(tempNode);
  tempNode.sheet.disabled = true;

  if (stylesheetElement.textContent !== '') {
    const textNode = document.createTextNode(stylesheetElement.textContent || '');
    tempNode.appendChild(textNode);
    const sheet = tempNode.sheet;
    const rules = [...sheet?.cssRules ?? []];
    stylesheetElement.textContent = this.rewrite(rules, prefix);
    tempNode.removeChild(textNode);
  }
}

function scopedCSSIsolation(appName, contentHtmlString) {
  contentHtmlString = contentHtmlString.trim();
  const containerElement = document.createElement('div');
  containerElement.innerHTML = contentHtmlString;
  const appElement = containerElement.firstChild;
  appElement.setAttribute('data-app-name', appName);
  const styleNodes = appElement.querySelectorAll('style') || [];
  [...styleNodes].forEach((stylesheetElement) => {
    processCSS(appElement, stylesheetElement, appName);
  });
  return appElement;
}

A test case demonstrates that the outer text remains red while the inner text becomes blue after the scoped CSS transformation.

Turning It Into a Web Component

To avoid repetitive container handling, the article wraps the isolation logic into a custom element isolation-content . The component reads data-app-name and data-isolation-mode attributes, builds the inner HTML, applies either shadowDOMIsolation or scopedCSSIsolation based on the mode, clears its original content, and appends the isolated element.

class Isolation extends HTMLElement {
  constructor() {
    super();
    const name = this.getAttribute('data-app-name');
    const mode = this.getAttribute('data-isolation-mode');
    const html = `
${this.innerHTML.trim()}
`;
    const appElement = mode === 'shadowDOM' ? shadowDOMIsolation(html) : scopedCSSIsolation(name, html);
    this.innerHTML = '';
    this.appendChild(appElement);
  }
}

customElements.define('isolation-content', Isolation);

The final HTML includes the three script files ( scopedCSSIsolation.js , shadowDOMIsolation.js , and Isolation.js ) and demonstrates both isolation modes side by side.

Summary

Qiankun style isolation offers two methods: Shadow DOM isolation (hard isolation) and Scoped CSS isolation (selector prefixing).

Shadow DOM leverages the browser's native shadow tree to completely separate styles.

Scoped CSS rewrites <style> rules by adding a unique container selector, achieving isolation without a shadow tree.

Both techniques can be encapsulated into a reusable Web Component for cleaner micro‑frontend integration.

qiankunmicro frontendsshadow-domScoped CSSCSS sandboxWeb Component
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.