What is the difference between Shadow DOM and Virtual DOM?

👨‍💻 Frontend Developer 🟠 May come up 🎚️ Medium
#React

Brief Answer

Shadow DOM and Virtual DOM are two different technologies for working with the DOM that solve similar but different tasks. Let’s figure out what’s what! 🤔

Shadow DOM:

  • Part of the web platform (native technology)
  • Isolates a component’s DOM tree from the main document
  • Provides style and markup encapsulation
  • Used in Web Components

Virtual DOM:

  • Concept implemented in libraries (React, Vue, etc.)
  • Creates a virtual representation of UI in memory
  • Optimizes real DOM updates
  • Simplifies working with application state

Key difference: Shadow DOM is about isolation, while Virtual DOM is about optimization! 🔥


Full Answer

Imagine you’re building a house. Shadow DOM is like building a separate room with its own walls and door that no one can see from other rooms. Virtual DOM is like making a blueprint of the house so you don’t have to rebuild everything each time, but only make necessary changes!

What is Shadow DOM

Shadow DOM is a native browser technology that allows creating isolated DOM trees inside elements:

// Creating Shadow DOM
class MyElement extends HTMLElement {
  constructor() {
    super();
    
    // Create Shadow DOM
    const shadow = this.attachShadow({ mode: 'open' });
    
    // The element's internals are isolated
    shadow.innerHTML = `
      <style>
        p { 
          color: blue; 
          font-weight: bold;
        }
      </style>
      <p>Hello from Shadow DOM! 🎉</p>
    `;
  }
}
 
// Register Web Component
customElements.define('my-element', MyElement);
 
// Usage
// <my-element></my-element>

What is Virtual DOM

Virtual DOM is a concept where a virtual representation of UI is created in memory:

// Example of&nbsp;how Virtual DOM works (simplified)
function render() {
  // This is&nbsp;Virtual DOM - an&nbsp;object in&nbsp;memory
  return {
    type: 'div',
    props: {
      className: 'container',
      children: [
        {
          type: 'h1',
          props: { children: 'Hello, world!' }
        },
        {
          type: 'p',
          props: { children: 'This is&nbsp;Virtual DOM in&nbsp;action!' }
        }
      ]
    }
  };
}
 
// Library (React) compares old and&nbsp;new Virtual DOM
// And&nbsp;applies only necessary changes to&nbsp;the real DOM

Comparison: Shadow DOM vs Virtual DOM

CharacteristicShadow DOMVirtual DOM
TypeNative browser technologyConcept/pattern
PurposeIsolation and encapsulationUpdate optimization
Where usedWeb ComponentsReact, Vue, Angular, etc.
Style isolation✅ Full❌ No
Rendering optimization❌ No✅ Yes
Learning complexityMediumLow

When to use Shadow DOM

Shadow DOM is perfect when you need:

// 1. Creating reusable Web Components
class DatePicker extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    // Styles are fully isolated!
    shadow.innerHTML = `
      <style>
        .calendar {
          border: 1px solid #ccc;
          border-radius: 4px;
          padding: 10px;
        }
        /* These styles won't affect external elements */
      </style>
      <div class="calendar">
        <!-- Calendar -->
      </div>
    `;
  }
}
 
// 2. When style encapsulation is&nbsp;needed
class ButtonComponent extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    shadow.innerHTML = `
      <style>
        button {
          background: blue;
          color: white;
          border: none;
          padding: 8px 16px;
          /* These styles don't conflict with globals */
        }
      </style>
      <button>
        <slot></slot> <!-- Slot for content -->
      </button>
    `;
  }
}

When to use Virtual DOM

Virtual DOM is your choice when:

// 1. Building complex interactive applications
function TodoApp() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Buy milk', done: false },
    { id: 2, text: 'Walk the dog', done: true }
  ]);
  
  // When state changes, Virtual DOM:
  // 1. Creates a&nbsp;new virtual tree
  // 2. Compares with the previous one
  // 3. Applies only necessary changes
  const toggleTodo = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  };
  
  return (
    <div>
      {todos.map(todo => (
        <div key={todo.id} className={todo.done ? 'done' : ''}>
          <input 
            type="checkbox" 
            checked={todo.done}
            onChange={() => toggleTodo(todo.id)}
          />
          <span>{todo.text}</span>
        </div>
      ))}
    </div>
  );
}
 
// 2. When update performance matters
function ExpensiveList({ items }) {
  // Virtual DOM optimizes list updates
  // Instead of&nbsp;full redraw, only changed items are updated
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name} - {item.value}
        </li>
      ))}
    </ul>
  );
}

Practical Examples

Shadow DOM in action

// Creating a&nbsp;custom element with Shadow DOM
class FancyCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    // Get attributes
    const title = this.getAttribute('title') || 'Title';
    const content = this.getAttribute('content') || 'Content';
    
    shadow.innerHTML = `
      <style>
        .card {
          border: 2px solid #3498db;
          border-radius: 8px;
          padding: 16px;
          margin: 10px;
          box-shadow: 0 4px 6px rgba(0,0,0,0.1);
          background: white;
          transition: transform 0.2s;
        }
        .card:hover {
          transform: translateY(-2px);
          box-shadow: 0 6px 12px rgba(0,0,0,0.15);
        }
        .title {
          color: #2c3e50;
          font-size: 1.2em;
          margin-bottom: 8px;
          font-weight: bold;
        }
        .content {
          color: #7f8c8d;
        }
      </style>
      <div class="card">
        <div class="title">${title}</div>
        <div class="content">${content}</div>
      </div>
    `;
  }
}
 
// Register component
customElements.define('fancy-card', FancyCard);
 
// Usage in&nbsp;HTML:
// <fancy-card 
//   title="My Card" 
//   content="Beautiful card with style isolation! 🎨">
// </fancy-card>

Virtual DOM in action

// React component with Virtual DOM
function UserDashboard({ users }) {
  const [filter, setFilter] = useState('');
  
  // Filtering users
  const filteredUsers = users.filter(user => 
    user.name.toLowerCase().includes(filter.toLowerCase())
  );
  
  // Virtual DOM makes this efficient!
  return (
    <div className="dashboard">
      <input 
        type="text" 
        placeholder="Search users..." 
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      />
      
      <div className="user-list">
        {filteredUsers.map(user => (
          <div key={user.id} className="user-card">
            <img src={user.avatar} alt={user.name} />
            <h3>{user.name}</h3>
            <p>{user.email}</p>
          </div>
        ))}
      </div>
    </div>
  );
}
 
// When typing text:
// 1. filter state updates
// 2. filteredUsers recalculated
// 3. Virtual DOM compares old and&nbsp;new tree
// 4. Only necessary changes applied to&nbsp;real DOM

Common Mistakes

1. Trying to use Shadow DOM as Virtual DOM

// ❌ Wrong - Shadow DOM doesn't optimize updates
class BadComponent extends HTMLElement {
  constructor() {
    super();
    this.shadow = this.attachShadow({ mode: 'open' });
    this.render(); // Full redraw on&nbsp;every update
  }
  
  render() {
    // This is&nbsp;inefficient for frequent updates!
    this.shadow.innerHTML = `
      <div>${this.getAttribute('data-value')}</div>
    `;
  }
}
 
// ✅ Correct - use Virtual DOM for frequent updates
function GoodComponent({ value }) {
  // Virtual DOM optimizes updates automatically
  return <div>{value}</div>;
}

2. Ignoring Shadow DOM encapsulation

// ❌ Style problem
class ProblematicElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    shadow.innerHTML = `
      <style>
        /* These styles might conflict if&nbsp;Shadow DOM isn't used properly */
        .button { 
          background: red; 
        }
      </style>
      <button class="button">Button</button>
    `;
  }
}
 
// ✅ Proper encapsulation
class GoodElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    shadow.innerHTML = `
      <style>
        /* Styles are fully isolated in&nbsp;Shadow DOM */
        button { 
          background: #3498db;
          color: white;
          border: none;
          padding: 8px 16px;
          border-radius: 4px;
        }
      </style>
      <button>
        <slot name="label">Button</slot>
      </button>
    `;
  }
}

Summary

Shadow DOM and Virtual DOM are like two different superheroes! 🦸‍♂️🦸‍♀️

Shadow DOM - the isolation hero:

  • Creates hidden, protected areas in the DOM
  • Ideal for Web Components
  • Provides style and markup encapsulation

Virtual DOM - the optimization hero:

  • Creates virtual copies of UI
  • Ideal for complex applications
  • Optimizes real DOM updates

When to use what:

  • Need encapsulation? Choose Shadow DOM! 🛡️
  • Need update optimization? Virtual DOM is at your service! ⚡

Both approaches make frontend development more powerful and convenient, just solving different tasks! 🚀


Want more awesome frontend articles? Subscribe to EasyAdvice, bookmark the site and level up every day! 💪