Ad – 728Γ—90
🌐 DOM & Browser

JavaScript DOM Manipulation – Working with the Document Object Model

The Document Object Model (DOM) is the live, in-memory tree representation of an HTML page. JavaScript can read and rewrite every node in that tree β€” changing text, attributes, styles, and structure β€” without a page reload. Mastering DOM manipulation is the gateway from static HTML to interactive web applications.

⏱️ 22 min read 🎯 Intermediate πŸ“… Updated 2026

What Is the DOM?

When a browser loads an HTML file it parses the markup and builds a tree of nodes. Each HTML element becomes an Element node, text content becomes a Text node, and comments become Comment nodes. The root of the tree is document.

The DOM is live β€” changes made via JavaScript are instantly reflected in the browser viewport and vice-versa. It is also language-agnostic; the DOM specification is maintained by the W3C/WHATWG independently of JavaScript, but JavaScript is its most common interface.

ℹ️
DOM vs HTML source

The DOM and the raw HTML source are not the same thing. The browser auto-corrects invalid HTML, JavaScript can modify the DOM after load, and browser extensions inject extra nodes β€” so the DOM you inspect in DevTools may differ from the original file.

Selecting Elements

Before you can manipulate an element you must obtain a reference to it. JavaScript provides multiple selection APIs:

MethodReturnsNotes
getElementById(id)Element | nullFastest for single ID lookup
getElementsByClassName(cls)HTMLCollection (live)Updates automatically when DOM changes
getElementsByTagName(tag)HTMLCollection (live)Pass "*" for all elements
querySelector(selector)Element | nullCSS selector, first match only
querySelectorAll(selector)NodeList (static)All matches; not live
JavaScript
// By ID (returns one element or null)
const title = document.getElementById('main-title');

// By class name (live HTMLCollection)
const cards = document.getElementsByClassName('card');

// CSS selector – first match
const firstBtn = document.querySelector('.btn-primary');

// CSS selector – all matches (static NodeList)
const allLinks = document.querySelectorAll('nav a');
allLinks.forEach(link => console.log(link.href));

// Scoped query – search inside an element
const nav = document.getElementById('main-nav');
const activeItem = nav.querySelector('.active');

Traversing the DOM

Once you have a reference you can navigate to related nodes using traversal properties:

JavaScript
const list = document.querySelector('ul');

// Parent
console.log(list.parentNode);        // immediate parent node
console.log(list.parentElement);     // same but always an Element

// Children (element-only, preferred)
console.log(list.children);          // HTMLCollection of child Elements
console.log(list.firstElementChild); // first <li>
console.log(list.lastElementChild);  // last <li>

// Siblings
const item = list.firstElementChild;
console.log(item.nextElementSibling);     // second <li>
console.log(item.previousElementSibling); // null (it's first)

// All nodes including Text/Comment
console.log(list.childNodes);   // NodeList including whitespace text nodes
console.log(list.firstChild);   // often a text node (whitespace)
πŸ’‘
Use Element properties, not Node properties

Prefer children, firstElementChild, nextElementSibling over childNodes, firstChild, nextSibling. The Node versions include whitespace text nodes and are rarely what you want.

Modifying Content

Three properties let you read and write element content. Each has different behaviour β€” choose deliberately:

JavaScript
const div = document.querySelector('#demo');

// innerHTML: parses HTML tags – powerful but XSS risk with user data
div.innerHTML = '<strong>Hello</strong> World';

// textContent: raw text, ignores tags – safe for user-supplied strings
div.textContent = '<script>alert(1)</script>'; // displays as text, not executed

// innerText: visible text only (respects CSS display:none, triggers reflow)
console.log(div.innerText); // reflects what user sees

// Differences at a glance:
// innerHTML  – fastest for bulk HTML, dangerous with untrusted input
// textContent– safe, no rendering cost for hidden elements
// innerText  – CSS-aware, slower (triggers layout)
β–Ά Output
div.innerHTML β†’ renders bold "Hello" + " World"
div.textContent β†’ displays literal <script> tag as text (safe)

Attributes and Dataset

HTML attributes are accessible through dedicated methods or the convenient dataset property for data-* attributes:

JavaScript
const img = document.querySelector('img');

// Standard attribute methods
console.log(img.getAttribute('src'));       // current src value
img.setAttribute('alt', 'A scenic view');   // set or create
img.removeAttribute('loading');             // remove attribute

// Property shortcut (for reflected properties)
img.src = 'photo.jpg';   // same as setAttribute for src
img.alt = 'Photo';

// data-* attributes via dataset (camelCase conversion)
// HTML: <div data-user-id="42" data-role="admin">
const card = document.querySelector('[data-user-id]');
console.log(card.dataset.userId);   // "42"  (data-user-id β†’ userId)
console.log(card.dataset.role);     // "admin"
card.dataset.role = 'editor';       // updates attribute in DOM

Modifying Styles and Classes

Inline styles are set via the style property; class-based styling is managed with classList:

JavaScript
const box = document.querySelector('.box');

// Inline style (camelCase property names)
box.style.backgroundColor = '#3498db';
box.style.fontSize = '18px';
box.style.transform = 'translateX(20px)';

// Read computed style (includes stylesheet rules)
const computed = getComputedStyle(box);
console.log(computed.display); // "flex" (even if set by CSS file)

// classList API β€” preferred over className string manipulation
box.classList.add('highlight');          // add class
box.classList.remove('hidden');          // remove class
box.classList.toggle('active');          // add if absent, remove if present
box.classList.toggle('active', true);    // force-add (2nd arg = boolean)
console.log(box.classList.contains('highlight')); // true
box.classList.replace('highlight', 'selected');   // swap classes

Creating and Inserting Elements

JavaScript
// createElement + appendChild (classic)
const li = document.createElement('li');
li.textContent = 'New item';
li.classList.add('list-item');
document.querySelector('ul').appendChild(li);

// prepend – insert as first child
const ul = document.querySelector('ul');
const firstItem = document.createElement('li');
firstItem.textContent = 'First!';
ul.prepend(firstItem);

// insertBefore(newNode, referenceNode)
const ref = ul.children[1];
const inserted = document.createElement('li');
inserted.textContent = 'Inserted before index 1';
ul.insertBefore(inserted, ref);

// insertAdjacentHTML – fastest for raw HTML strings
// positions: 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend'
ul.insertAdjacentHTML('beforeend', '<li class="list-item">Last via HTML</li>');

// cloneNode(deep) – duplicate an element
const clone = li.cloneNode(true); // true = deep clone (includes children)
ul.appendChild(clone);
β–Ά Output
Resulting list:
β€’ First!
β€’ New item (original)
β€’ Inserted before index 1
β€’ (original index 1 item)
β€’ ...
β€’ Last via HTML

Removing Elements

JavaScript
// Modern: element.remove() β€” supported in all modern browsers
const obsolete = document.getElementById('old-banner');
obsolete.remove();

// Legacy: parentNode.removeChild(child)
const item = document.querySelector('.to-delete');
item.parentNode.removeChild(item);

// Remove all children efficiently
const container = document.querySelector('#results');
container.innerHTML = '';         // fast but may leak event listeners
// Alternative (preserves listeners on children if any):
while (container.firstChild) {
  container.removeChild(container.firstChild);
}
⚠️
XSS Warning with innerHTML

Never insert raw user-supplied strings using innerHTML. An attacker can inject <script> tags or event handlers. Always use textContent for user data, or sanitize with a library like DOMPurify.

Practical Exercise

πŸ‹οΈ Practical Exercise

Build a dynamic to-do list using only DOM APIs:

  1. Create an <input> and an <ul> in your HTML.
  2. When the user presses Enter in the input, create a new <li> with the input's text and append it to the list.
  3. Each <li> should have a "Delete" <button>; clicking it calls .remove() on the parent <li>.
  4. Clicking the <li> text toggles a done CSS class that applies text-decoration: line-through.

πŸ”₯ Challenge Exercise

Extend the to-do list with drag-and-drop reordering using only the draggable attribute and dragstart / dragover / drop events. Store the dragged element reference and use insertBefore to reorder items on drop.

Summary

πŸ“‹ Summary

  • The DOM is a live tree of nodes representing the parsed HTML page.
  • Use querySelector / querySelectorAll for flexible CSS-selector-based selection.
  • Traverse with children, firstElementChild, nextElementSibling (element-only variants).
  • Modify content with textContent (safe) or innerHTML (powerful, XSS risk).
  • Manage attributes via getAttribute/setAttribute and data-* via dataset.
  • Prefer classList over manipulating className strings directly.
  • Create nodes with createElement, insert with append/prepend/insertAdjacentHTML.
  • Remove nodes with element.remove() (modern) or parent.removeChild() (legacy).

Interview Questions

  • What is the difference between innerHTML, textContent, and innerText?
  • What does "the DOM is live" mean? How does a live HTMLCollection differ from a static NodeList?
  • How would you efficiently remove all child elements from a container?
  • What is the difference between an HTML attribute and a DOM property?
  • Why is inserting user-supplied strings with innerHTML dangerous?

Frequently Asked Questions

What is the difference between querySelector and getElementById? +

getElementById is slightly faster because it uses the browser's internal ID index. querySelector accepts any CSS selector so it is far more flexible. For a single ID lookup either works; for anything more complex use querySelector.

Can I call DOM methods before the page fully loads? +

If your <script> is in <head> without defer or async, the DOM is not yet built and querySelector will return null. Place scripts at the bottom of <body>, use the defer attribute, or wrap code in a DOMContentLoaded event listener.

What is insertAdjacentHTML and when should I use it? +

insertAdjacentHTML parses an HTML string and inserts nodes at a specified position relative to an element without replacing existing content. It is faster than setting innerHTML because it does not re-parse the entire element β€” ideal for appending rows to a table or items to a list.

Ad – 336Γ—280