Blog

Shadow DOM vs Virtual DOM

Key Takeaways

  • Shadow DOM encapsulates a component’s real DOM and scopes its styles; a virtual DOM describes UI for a library renderer.
  • Virtual DOM reconciliation generally compares new and previous renderer state, then commits changes to the real DOM.
  • React and Vue use virtual-DOM-based rendering strategies. Angular’s standard rendering model uses change detection and template bindings instead.
  • You can use both concepts together, but neither automatically makes an application faster.

Shadow DOM vs virtual DOM is a comparison of encapsulation and rendering strategy, not two competing DOM implementations. Shadow DOM creates a browser-managed boundary around component internals. A virtual DOM is a library-managed description of the UI used to calculate updates to the browser’s DOM.

The practical distinction: virtual DOM reconciliation helps a renderer decide what to update; Shadow DOM defines a component boundary for DOM access and styling. Neither is a general-purpose performance feature.

Shadow DOM vs Virtual DOM: The Differences

AspectShadow DOMVirtual DOM
Primary purposeComponent encapsulation and style scopingDescribing UI and calculating host-DOM updates
OwnershipThe browser implements the boundary; component code supplies its contentsA library manages its representation and update algorithm
Browser API and supportNative APIs such as attachShadow(); core support is widespreadNo browser virtual-DOM API; behavior depends on the renderer
Underlying structureReal DOM nodes rooted at a ShadowRoot attached to a hostJavaScript data structures representing requested UI
EncapsulationScopes ordinary CSS selectors and DOM traversalDoes not provide a CSS or DOM-access boundary by itself
Effect on renderingIts real nodes participate in browser renderingIts renderer commits real-DOM changes before the browser displays the result

The two useful starting points are MDN’s Shadow DOM guide and Vue’s rendering-mechanism guide. They describe different layers of the same application.

What Is the Virtual DOM?

A virtual DOM is an in-memory description of desired UI. It is a rendering pattern, not a browser feature, and there is no single standard implementation.

Think of it as instructions for a renderer, not a duplicate of the entire live document. For example, a React element describes what should be displayed; creating that description does not itself create a browser DOM element.

How Virtual DOM Reconciliation Works

The usual update model has three conceptual stages:

  1. Render: evaluate components or render functions to produce a new UI description.
  2. Reconcile: compare that description with previous virtual-tree or renderer state to determine the required changes.
  3. Commit: apply the resulting operations to the host environment—in a browser application, usually the DOM.

Vue explicitly describes comparing a new virtual tree with the old tree. React’s render-and-commit explanation likewise distinguishes calculating output from applying DOM changes. The stages and internal data structures vary between renderers.

Library rendering                 Browser encapsulation

previous state + next UI          <notice-card>  (host)
           |                            |
      reconciliation                #shadow-root
           |                            |
       DOM commit                    real nodes
           |                        scoped styles
           v
browser rendering work

Re-running a component does not necessarily change the DOM. If the relevant output is unchanged, a renderer can leave the existing nodes alone. Conversely, changing a DOM node does not mean that the browser redraws only that node: browser rendering is a separate process.

Batching Is Scheduling, Not Encapsulation

A framework can group state updates before processing them. For example, React batches state updates from an event handler rather than processing each setter as a separate completed screen update.

That is a scheduling policy, not something guaranteed by the existence of a virtual tree. Keep three questions separate: when the framework processes updates, which DOM operations it commits, and what work the browser performs afterward.

What Is the Shadow DOM?

Shadow DOM attaches a separate, real DOM tree to an element called the host. That tree starts at a shadow root. The host’s ordinary children are commonly called its light DOM.

Document-level selectors do not ordinarily reach into a shadow tree, and ordinary selectors inside the shadow tree do not select arbitrary elements outside it. Likewise, a document-level DOM query does not automatically traverse shadow roots. This is encapsulation, not an offscreen representation awaiting reconciliation.

A native range slider is not a good authoring example: its styling hooks include browser-specific behavior. An author-created custom element makes the boundary much clearer. For another introduction, see our quick explanation of Shadow DOM.

Create a Native Custom Element With attachShadow()

Serve these two files from the same directory using a local static server. The example needs no framework or build step. Custom elements are registered with customElements.define(); attachShadow() creates and returns the shadow root.

index.html

<style>
  strong { text-decoration: underline; }
  notice-card { --notice-accent: rebeccapurple; }
  notice-card::part(frame) { border-radius: 0.5rem; }
</style>

<notice-card>
  <strong>Deployment complete.</strong>
</notice-card>

<p><strong>Outside the component.</strong></p>

<script type="module" src="./notice-card.js"></script>

notice-card.js

class NoticeCard extends HTMLElement {
  constructor() {
    super();
    const root = this.attachShadow({ mode: "open" });

    const style = document.createElement("style");
    style.textContent = `
      :host { display: block; }
      .frame {
        border: 2px solid var(--notice-accent, steelblue);
        padding: 1rem;
      }
      strong { color: crimson; }
    `;

    const frame = document.createElement("div");
    frame.className = "frame";
    frame.setAttribute("part", "frame");

    const label = document.createElement("strong");
    label.textContent = "Status: ";

    frame.append(label, document.createElement("slot"));
    root.append(style, frame);
  }
}

customElements.define("notice-card", NoticeCard);

The component’s internal “Status:” label is crimson but not underlined. Both light-DOM strong elements are underlined by the page stylesheet. The internal frame has a purple border and rounded corners through the component’s explicit styling interfaces.

Slots Connect Light DOM and Shadow Content

The <slot> displays content supplied by the component’s consumer. In this example, the “Deployment complete.” element remains a light-DOM child of the host; slot assignment does not move it into the shadow tree.

For named slots, a direct child with slot="title" is assigned to a matching <slot name="title">. The templates-and-slots guide covers named slots and fallback content.

Keep these structures distinct:

  • Document tree: contains the host and its ordinary light-DOM children.
  • Shadow tree: contains the component’s real implementation nodes under its shadow root.
  • Composed presentation: the combined result of shadow content and slot-assigned content. CSS specifies a flattened element tree for inheritance and box construction; this does not change the nodes’ DOM parent relationships.
  • Virtual representation: a renderer’s UI description, independent of those browser tree relationships.

The CSS Shadow Module editor’s draft explains the flattened-tree model. It is a specification work in progress, not a framework rendering API.

Style Scoping Has Deliberate Openings

Shadow DOM limits selector matching; it does not eliminate inheritance or make a component impossible to theme.

  • Inherited properties: properties such as color can inherit from the host into shadow content.
  • CSS custom properties: an inherited value such as --notice-accent lets consumers supply a theme token that internal CSS uses.
  • Exposed parts: part="frame" lets outside CSS target that internal element through the ::part() pseudo-element.

Notice that the shadow stylesheet’s ordinary strong selector does not select the slotted strong element. That element still belongs to the light DOM. Treat theme tokens and exposed parts as a component API, rather than encouraging consumers to depend on internal selectors.

Open and Closed Roots Control Access, Not Security

With mode: "open", the host’s shadowRoot property exposes the root. With mode: "closed", that property returns null; code retaining the reference returned by attachShadow() can still use it. Both modes provide style scoping.

Events Can Cross the Shadow Boundary

The composed property controls whether an event can cross a shadow boundary. For a custom event intended to bubble from an internal node to document listeners, set both bubbles: true and composed: true.

Event targets are also retargeted: for an event originating on an internal node, an outside listener normally sees the host as its target. The DOM Standard defines this behavior. This matters when you write event delegation or click-outside logic.

How React, Vue, and Angular Fit In

React and Vue use virtual-DOM-based rendering strategies, but their implementations differ. That does not mean their components automatically have shadow roots. For the broader framework comparison, see Vue vs React.

Angular’s standard renderer is not a React- or Vue-style virtual-DOM renderer. Angular uses change detection to determine whether application state requires DOM updates, with templates and bindings expressing those updates. Its runtime-performance documentation describes that model.

Angular’s view encapsulation modes are a separate concern. ViewEncapsulation.ShadowDom uses native shadow roots. The default ViewEncapsulation.Emulated scopes component styles using generated attributes; it does not create a native shadow root.

Can Shadow DOM and Virtual DOM Work Together?

Yes. For example, React supports custom HTML elements, so a React-rendered application can contain the <notice-card> element above. React can manage the host and supplied light-DOM content while the custom element manages its shadow tree.

Keep ownership clear: avoid having two renderers independently mutate the same nodes. Consuming a custom element does not require the surrounding framework to reconcile its internal shadow tree.

Performance: Measure the Work, Not the Label

Neither technology guarantees faster rendering. Virtual-DOM rendering introduces description-building and reconciliation work. Shadow DOM still contains real nodes that need styling, layout, and painting; encapsulation does not guarantee that recalculation stays inside the component.

A framework DOM commit and a browser paint are different events. The browser may need style calculation, layout, paint, or compositing work, and a change to one element can affect the geometry of others. MDN’s critical-rendering-path guide explains why DOM operations alone are not the whole cost.

For a slow interaction, profile a production build and separate JavaScript work from browser rendering work. Then change the measured bottleneck and repeat the same interaction. Our JavaScript performance tips provide a broader optimization checklist.

Common Mistakes

  • Calling a framework component a shadow root. A component abstraction does not imply native DOM encapsulation.
  • Equating a component re-render with a full-page redraw. Rendering, DOM commits, and browser painting are separate work.
  • Expecting page CSS to reach internal nodes. Use the component’s documented theme tokens and parts.
  • Choosing either technology as an automatic optimization. Choose encapsulation for component boundaries and evaluate rendering strategies against your workload.

FAQ

What is the difference between Shadow DOM and virtual DOM?

Shadow DOM is a browser feature that encapsulates a component’s real DOM and styles. A virtual DOM is a library-managed UI description used to calculate and commit updates.

Does the virtual DOM compare against the real DOM?

Usually, reconciliation compares a new virtual tree with previous virtual-tree or renderer state, then applies changes to the real DOM. Comparing against the live DOM is not the usual update model; implementations differ.

Does Angular use a virtual DOM?

No. Angular’s standard renderer uses change detection and template bindings rather than a React- or Vue-style virtual DOM. Its ShadowDom encapsulation mode is a separate choice.

Can Shadow DOM and virtual DOM be used together?

Yes. A virtual-DOM-rendered application can contain custom elements with shadow roots, with the application managing the host and light DOM while each component manages its shadow tree.

Does Shadow DOM improve performance?

Not inherently. Shadow DOM provides encapsulation and style scoping, not a guarantee of less layout, paint, or JavaScript work; profile your application before treating it as an optimization.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me