JavaScript DOM Traversal
DOM traversal means moving through the structure of an HTML page using JavaScript — going up to parent elements, down to children, or sideways to siblings. The browser represents every HTML page as a tree of nodes, and traversal lets you navigate that tree without hardcoding selectors for every element.
The DOM as a Family Tree
Think of HTML elements as a family. A <div> containing two <p> tags is a parent with two children. The two <p> tags are siblings. Traversal moves you through these relationships dynamically.
Diagram: DOM Tree Structure
<div id="container">
<h2>Title</h2>
<p>First paragraph</p>
<p>Second paragraph</p>
</div>
div#container
/ | \
h2 p p
"Title" "First" "Second"
h2 and p are children of div
h2, p, p are siblings of each other
div is the parent of all three
Moving to the Parent
parentElement
let p = document.querySelector("p");
console.log(p.parentElement); // the div containing p
// Chain up multiple levels
console.log(p.parentElement.parentElement); // the parent of the div
closest( ) — Walk Up to the First Match
closest() starts from the current element and walks up the DOM tree, returning the first ancestor that matches the given selector.
<!-- HTML -->
<div class="card">
<div class="card-body">
<button class="btn-delete">Delete</button>
</div>
</div>
let btn = document.querySelector(".btn-delete");
let card = btn.closest(".card");
console.log(card); // the div.card element
Diagram: closest() Walking Up
Start: .btn-delete .btn-delete → is it .card? NO → go up .card-body → is it .card? NO → go up .card → is it .card? YES → return it
Moving to Children
children — Only Element Nodes
let container = document.querySelector("#container");
console.log(container.children); // HTMLCollection of child elements
console.log(container.children[0]); // first child element
console.log(container.children.length); // number of children
firstElementChild / lastElementChild
let list = document.querySelector("ul");
console.log(list.firstElementChild); // first <li>
console.log(list.lastElementChild); // last <li>
childNodes — Includes Text Nodes Too
childNodes includes text nodes (whitespace, newlines) between elements. Use children when you want only element nodes.
// childNodes can return unexpected text nodes
// children only returns actual HTML elements — usually safer
Moving to Siblings
nextElementSibling / previousElementSibling
<!-- HTML -->
<ul>
<li>Item 1</li>
<li id="second">Item 2</li>
<li>Item 3</li>
</ul>
let second = document.getElementById("second");
console.log(second.nextElementSibling.textContent); // "Item 3"
console.log(second.previousElementSibling.textContent); // "Item 1"
Diagram: Sibling Navigation
<li>Item 1</li> ← previousElementSibling <li>Item 2</li> ← current element (second) <li>Item 3</li> ← nextElementSibling
Full Traversal Reference
| Property / Method | Direction | Returns |
|---|---|---|
parentElement | Up | Parent element |
closest(selector) | Up | First matching ancestor |
children | Down | All child elements |
firstElementChild | Down | First child element |
lastElementChild | Down | Last child element |
nextElementSibling | Sideways | Next sibling element |
previousElementSibling | Sideways | Previous sibling element |
Practical Example: Highlight the Clicked List Item and Its Neighbors
let items = document.querySelectorAll("li");
items.forEach(function(item) {
item.addEventListener("click", function() {
// Remove previous highlights
items.forEach(i => i.style.background = "");
// Highlight the clicked item
item.style.background = "yellow";
// Highlight the next sibling
if (item.nextElementSibling) {
item.nextElementSibling.style.background = "lightyellow";
}
// Highlight the previous sibling
if (item.previousElementSibling) {
item.previousElementSibling.style.background = "lightyellow";
}
});
});
Diagram: Click on "Item 2" Result
Before click:
[ Item 1 ] [ Item 2 ] [ Item 3 ]
After clicking "Item 2":
[ Item 1 lightyellow ] [ Item 2 yellow ] [ Item 3 lightyellow ]
previousSibling clicked nextSibling
Event Delegation with Traversal
Instead of attaching listeners to every child, attach one listener to the parent and use traversal to find what was clicked.
<!-- HTML -->
<ul id="todo-list">
<li>Buy groceries <button class="done-btn">Done</button></li>
<li>Read a book <button class="done-btn">Done</button></li>
</ul>
let list = document.getElementById("todo-list");
list.addEventListener("click", function(event) {
if (event.target.classList.contains("done-btn")) {
// Walk up from the button to its parent li
let listItem = event.target.closest("li");
listItem.style.textDecoration = "line-through";
}
});
Diagram: Event Delegation Flow
User clicks "Done" button inside li:
event.target = .done-btn button
│
.closest("li") walks up
│
Finds parent <li>
│
Applies strikethrough to the whole li
Looping Over Children
let nav = document.querySelector("nav");
// Loop over all direct children
Array.from(nav.children).forEach(function(child) {
console.log(child.tagName, child.textContent);
});
Summary
DOM traversal lets you move through an HTML page's tree structure without querying the DOM every time with a new selector. Use parentElement and closest() to go up, children and firstElementChild to go down, and nextElementSibling with previousElementSibling to move sideways. Traversal is especially powerful for event delegation, where a single parent listener handles events for many children dynamically.
