JavaScript: DOM Manipulation
Select, modify, create, remove, and traverse HTML elements using the browser's Document Object Model API.
What is the DOM?
The DOM (Document Object Model) is a live, tree-structured representation of an HTML page that the browser creates when it loads the document. JavaScript uses the DOM API to read and change elements, attributes, styles, and content without reloading the page.
Document Object Model
A programming interface that represents the page as a tree of nodes JavaScript can read and manipulate.
- Every HTML tag becomes a node in the tree
- The root of the tree is
document - Changes to DOM nodes are reflected instantly in the browser
- The DOM is not the HTML source: it is a live object graph built from it
The document Object
document is the root of the DOM tree: everything on the page is reachable from it.
Press Run to execute the code and see output here.
DOM Tree Structure
The browser parses HTML into a tree of nodes. Element nodes represent tags, text nodes hold visible text, and attribute nodes store tag attributes. Understanding the tree is essential for traversal and targeting the right element.
Node Types
The DOM tree is made of different node types: element nodes are the most commonly manipulated.
- Element node: represents an HTML tag, e.g.
<div>,<p> - Text node: the text content inside an element
- Document node: the root
documentobject - Every node has a
nodeTypeproperty: 1 for elements, 3 for text, 9 for document
DOM Tree Example
Each tag is an element node. Text inside is a text node child.
<div id="root">
<h1>Title</h1>
<p>Paragraph text</p>
<ul>
<li>Item one</li>
<li>Item two</li>
</ul>
</div>Selecting Elements
Before modifying the DOM you must select the element. The modern standard is querySelector and querySelectorAll: they accept any CSS selector. The older methods (getElementById etc.) are still widely seen.
Selection Methods
querySelector and querySelectorAll cover every use case with familiar CSS selector syntax.
getElementById("id"): returns one element or nullgetElementsByClassName("cls"): live HTMLCollectiongetElementsByTagName("tag"): live HTMLCollectionquerySelector(".cls"): first match or null (static)querySelectorAll(".cls"): static NodeList of all matches
Selecting Elements
Try all five selection methods against this HTML.
const title = document.getElementById("title");
const notes = document.getElementsByClassName("note");
const items = document.getElementsByTagName("li");
const first = document.querySelector(".note");
const all = document.querySelectorAll(".note");
console.log(title.textContent); // "Hello"
console.log(notes.length); // 2
console.log(items.length); // 2
console.log(first.textContent); // "First note"
console.log(all.length); // 2innerHTML, textContent, innerText
These three properties read or set the content of an element. They differ in how they handle HTML tags and whitespace: choosing the wrong one can introduce XSS vulnerabilities or unexpected formatting.
Content Properties
Use textContent for plain text. Only use innerHTML when you intend to insert real HTML markup.
innerHTML: parses the string as HTML: risky with user input (XSS)textContent: treats content as plain text, ignores tags: safe and fastinnerText: like textContent but respects CSS visibility and formatting- Never set
innerHTMLfrom untrusted user input without sanitising first
innerHTML vs textContent
Watch how each property handles the same tag string differently.
const box = document.getElementById("box");
const info = document.getElementById("info");
// innerHTML parses tags
box.innerHTML = "<strong>Bold text</strong> and <em>italic</em>";
// textContent treats tags as plain text
info.textContent = "<strong>This tag is NOT parsed</strong>";
console.log(box.textContent); // "Bold text and italic", strips tags
console.log(info.innerHTML); // "<strong>...", escapedsetAttribute() and getAttribute()
Attributes live on the HTML tag itself. getAttribute reads them as strings. setAttribute writes them. removeAttribute deletes them entirely.
Attributes vs Properties
Attributes are the HTML source values. Properties are the live JS object values. They can differ.
getAttribute("href"): returns the literal string from the HTMLsetAttribute("class", "active"): sets the attribute on the elementremoveAttribute("disabled"): removes the attribute completelyhasAttribute("hidden"): returns boolean
setAttribute and getAttribute
Read, update, and remove attributes on these two elements.
const link = document.getElementById("link");
const btn = document.getElementById("btn");
console.log(link.getAttribute("href")); // "/home"
console.log(link.getAttribute("data-section")); // "intro"
link.setAttribute("href", "/about");
link.setAttribute("target", "_blank");
console.log(link.getAttribute("href")); // "/about"
btn.removeAttribute("disabled");
console.log(btn.hasAttribute("disabled")); // falsestyle Property
Every element has a style object for reading and writing inline CSS. Properties are camelCase in JavaScript (backgroundColor not background-color). Prefer toggling CSS classes over writing inline styles directly.
element.style
Direct style manipulation works but class toggling is cleaner and easier to maintain.
- CSS properties are camelCase:
fontSize,backgroundColor,borderRadius element.style.color = "red"writes an inline style- To read computed styles (from stylesheets): use
getComputedStyle(el) - Prefer
classList.add("highlight")over setting styles directly
Direct Style Manipulation
Click the button to toggle inline styles on the paragraph.
const msg = document.getElementById("msg");
const toggle = document.getElementById("toggle");
let styled = false;
toggle.addEventListener("click", () => {
if (styled) {
msg.style.color = "";
msg.style.background = "";
msg.style.padding = "";
} else {
msg.style.color = "white";
msg.style.background = "steelblue";
msg.style.padding = "8px 12px";
}
styled = !styled;
});classList
The classList API is the standard way to manage CSS classes. It provides add, remove, toggle, and contains: all more reliable than manipulating className as a string.
classList Methods
classList is the preferred way to control classes: it never accidentally removes other classes.
classList.add("cls"): adds the classclassList.remove("cls"): removes the classclassList.toggle("cls"): adds if absent, removes if presentclassList.contains("cls"): returns booleanclassList.replace("old", "new"): swaps one class for another
classList in Action
Click the button to toggle the active class on the paragraph.
const status = document.getElementById("status");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
status.classList.toggle("active");
if (status.classList.contains("active")) {
status.textContent = "Active";
} else {
status.textContent = "Inactive";
}
});createElement(), appendChild(), append()
To add new content dynamically, create an element with document.createElement(), configure it, then insert it into the DOM. append() is the modern method and accepts both nodes and strings.
Creating and Inserting Elements
Create, configure, then insert. The element only appears in the page after insertion.
document.createElement("div"): creates a detached elementparent.appendChild(el): inserts as the last child (accepts Node only)parent.append(el, "text"): modern: accepts Nodes and strings, adds multipleparent.prepend(el): inserts as the first child
createElement and append
Each click creates a new li and appends it to the list.
const list = document.getElementById("list");
const btn = document.getElementById("add");
let count = 1;
btn.addEventListener("click", () => {
const li = document.createElement("li");
li.textContent = "New item " + count++;
li.style.color = "steelblue";
list.append(li);
});removeChild() and remove()
Elements can be removed from the DOM with element.remove() (modern) or parent.removeChild(child) (older). Removal takes the element out of the document: it is not destroyed if you hold a reference to it.
Removing Elements
remove() is the modern, self-contained method. removeChild() requires a reference to the parent.
el.remove(): removes itself from the DOM (ES2015+)parent.removeChild(child): older approach, returns the removed node- The removed element still exists in memory if a JS variable holds it
- Re-inserting a removed element is valid: just append it somewhere again
Removing Elements
Click X on any item to remove it from the list.
document.querySelectorAll(".del").forEach(btn => {
btn.addEventListener("click", () => {
btn.parentElement.remove();
});
});insertBefore(), insertAdjacentHTML(), cloneNode()
For precise insertion control, insertBefore places a node before a reference child. insertAdjacentHTML inserts raw HTML at four named positions around an element. cloneNode duplicates an element.
Precise Insertion and Cloning
insertAdjacentHTML is the most flexible insertion method: four positions around any element.
parent.insertBefore(newEl, refEl): inserts newEl before refElel.insertAdjacentHTML("beforebegin"): before the element itselfel.insertAdjacentHTML("afterbegin"): inside, before first childel.insertAdjacentHTML("beforeend"): inside, after last childel.insertAdjacentHTML("afterend"): after the element itselfel.cloneNode(true): deep clone including all descendants
insertBefore and cloneNode
Inserts a First item before Second, then cloning adds duplicates.
const list = document.getElementById("list");
const second = document.getElementById("second");
const btn = document.getElementById("clone");
// Insert before "Second"
const first = document.createElement("li");
first.textContent = "First (inserted)";
list.insertBefore(first, second);
// insertAdjacentHTML at the end of the list
list.insertAdjacentHTML("beforeend", "<li>Third (adjacent)</li>");
// Clone last item on each click
btn.addEventListener("click", () => {
const last = list.lastElementChild;
list.append(last.cloneNode(true));
});Traversing the DOM
DOM traversal properties let you navigate the tree relative to a known element: without querying by selector. This is useful for event delegation and working with adjacent sibling elements.
Traversal Properties
Navigate up, down, and sideways in the tree from any element.
el.parentElement: the direct parent elementel.children: live HTMLCollection of child elements (no text nodes)el.firstElementChild/lastElementChild: first and last child elementsel.nextElementSibling/previousElementSibling: adjacent siblings- All return
nullwhen the position does not exist
DOM Traversal
Navigate the list structure using traversal properties.
const b = document.getElementById("b");
console.log(b.parentElement.id); // "nav"
console.log(b.previousElementSibling.id); // "a"
console.log(b.nextElementSibling.id); // "c"
const nav = document.getElementById("nav");
console.log(nav.children.length); // 3
console.log(nav.firstElementChild.id); // "a"
console.log(nav.lastElementChild.id); // "c"
// Walk all children
for (const child of nav.children) {
child.style.color = "steelblue";
}Knowledge Check
1. What does the DOM represent?
2. What does querySelector() return when no match is found?
3. What is the difference between textContent and innerHTML?
4. Which classList method checks if a class is present on an element?
5. What is the difference between append() and appendChild()?
6. What does cloneNode(true) do?
7. What does parentElement return for the root <html> element?
8. Which method returns a live HTMLCollection that updates automatically?