Beginner40 minutesJavaScriptDOM

DOM Traversal: Navigating the Family Tree

Learning Objectives

By the end of this lesson, you'll be able to:

  • Explain Explain the parent–child–sibling relationships in the DOM tree
  • Use parentNode and parentElement to move up the tree
  • Use children, firstElementChild, and lastElementChild to move down
  • Use nextElementSibling and previousElementSibling to move sideways
  • Explain Explain the difference between Node properties (childNodes) and Element properties (children)
  • Combine traversal methods to reach any element from any starting point
  • Apply Apply DOM traversal in a real-world scenario instead of querying from document every time

Why This Matters:

DOM traversal lets you write shorter, faster code by walking between related elements instead of repeatedly searching the entire document.

The DOM Family Tree

Every element in the DOM has relationships with other elements, just like people in a family tree. The language we use is the same:

  • Parent — the element that directly contains this element
  • Children — the elements directly inside this element
  • Siblings — elements that share the same parent
  • Ancestors — all elements above this one (parent, grandparent, …)
  • Descendants — all elements below this one (children, grandchildren, …)

Consider this HTML:

<body>
  <header>
    <h1>My Site</h1>
    <nav>
      <a href="#">Home</a>
      <a href="#">About</a>
    </nav>
  </header>
  <main>
    <section>
      <h2>Welcome</h2>
      <p>Hello world</p>
    </section>
    <article>
      <h2>News</h2>
      <p>Latest post</p>
    </article>
  </main>
  <footer>
    <p>&copy; 2025</p>
  </footer>
</body>

In this structure:

  • <body> is the parent of <header>, <main>, and <footer>
  • <header>, <main>, and <footer> are siblings of each other
  • <section> and <article> are children of <main>
  • <h2> inside <section> is a grandchild (descendant) of <main>
bodyheadermainfooterh1navsectionarticleh2pParentChildrenGrandchildren← siblings →

Moving Up: Parent Properties

To move up the tree from an element to the element that contains it, use:

const section = document.querySelector('section');

// parentElement — always returns an element (or null)
section.parentElement;   // <main>

// parentNode — may return a non-element node (rare edge case)
section.parentNode;      // <main> (same in this case)

Which should you use? Prefer parentElement. It guarantees you get an element back (or null if you’ve reached the top). parentNode can return the document node above <html>, which is rarely what you want.

Climbing Multiple Levels

You can chain parentElement to climb several levels at once:

const h2 = document.querySelector('section h2');

h2.parentElement;                     // <section>
h2.parentElement.parentElement;        // <main>
h2.parentElement.parentElement.parentElement; // <body>

closest() — The Smart Climber

Instead of chaining parentElement repeatedly, use closest() to jump straight to the nearest ancestor that matches a CSS selector:

const h2 = document.querySelector('section h2');

// Find the nearest ancestor that is a <main>
h2.closest('main');      // <main>

// Find the nearest ancestor with a class
h2.closest('.container'); // null (none found)

// closest() checks the element itself too
h2.closest('h2');         // the h2 itself

Real-world use: closest() is incredibly useful in event handling. When a user clicks a button inside a card, you can use event.target.closest('.card') to find the card that was clicked, no matter how deeply nested the button is.

Moving Down: Child Properties

To move down the tree from a parent to its contents:

PropertyReturnsIncludes text nodes?
childrenHTMLCollection of child elementsNo
childNodesNodeList of all child nodesYes
firstElementChildFirst child elementNo
lastElementChildLast child elementNo
firstChildFirst child node (any type)Yes
lastChildLast child node (any type)Yes
const mainEl = document.querySelector('main');

// children — only element nodes (what you usually want)
mainEl.children;              // [section, article]
mainEl.children.length;       // 2
mainEl.children[0];           // <section>

// First and last shortcuts
mainEl.firstElementChild;     // <section>
mainEl.lastElementChild;      // <article>

// childNodes includes whitespace text nodes!
mainEl.childNodes;            // [text, section, text, article, text]
mainEl.childNodes.length;     // 5 (three text nodes for whitespace)

Common trap: childNodes includes invisible whitespace text nodes (the line breaks between your HTML tags). This is the number-one gotcha for beginners. Almost always use children instead.

Looping Through Children

const nav = document.querySelector('nav');

// children returns an HTMLCollection — use for...of
for (const link of nav.children) {
  console.log(link.textContent);
}

// Or convert to an array for .forEach, .map, .filter
const linksArray = Array.from(nav.children);
linksArray.forEach(link => {
  link.classList.add('nav-link');
});

⏸️ Check Your Understanding: Parents & Children

Before moving forward, can you answer these?

  1. What is the difference between parentNode and parentElement?
  2. You use mainEl.childNodes.length and get 5, but there are only 2 child elements. Why?
  3. How would you get all child elements of a <nav> and add a class to each one?
Check Your Answers
  1. parentElement always returns an element node (or null at the top of the tree). parentNode can return a non-element node like the document node above <html>. In practice, parentElement is almost always what you want.
  2. childNodes includes all node types, including invisible whitespace text nodes (the line breaks and spaces between your HTML tags). Use children instead — it returns only element nodes.
  3. Use Array.from(nav.children).forEach(child => child.classList.add("nav-item")). You need Array.from() because children returns an HTMLCollection, not an array.

How confident are you with this concept?

😕 Still confused | 🤔 Getting there | 😊 Got it! | 🎉 Could explain it to a friend!

Moving Sideways: Sibling Properties

Siblings are elements that share the same parent. You can step from one sibling to the next or previous:

const mainEl = document.querySelector('main');

// Previous sibling element
mainEl.previousElementSibling;   // <header>

// Next sibling element
mainEl.nextElementSibling;       // <footer>

// At the edges, you get null
const header = document.querySelector('header');
header.previousElementSibling;    // null (no sibling before it)

Element vs Node: Just like with children, always use the Element versions (nextElementSibling, previousElementSibling). The plain nextSibling / previousSibling may return whitespace text nodes.

Walking All Siblings

There is no built-in “give me all siblings” property, but you can get them by going up to the parent and then back down to its children:

const mainEl = document.querySelector('main');

// Get all siblings (including mainEl itself)
const allSiblings = mainEl.parentElement.children;
// [header, main, footer]

// Get only the OTHER siblings (exclude mainEl)
const otherSiblings = Array.from(allSiblings)
  .filter(el => el !== mainEl);
// [header, footer]

Nodes vs Elements: Why It Matters

This is the most confusing part of DOM traversal for beginners. The DOM has two parallel sets of traversal properties:

Node properties (all nodes)Element properties (elements only)
parentNodeparentElement
childNodeschildren
firstChildfirstElementChild
lastChildlastElementChild
nextSiblingnextElementSibling
previousSiblingpreviousElementSibling

Node properties include everything — elements, text nodes (including whitespace), and comments. Element properties skip all non-element nodes and give you only the HTML tags.

// Given this HTML: <ul> <li>A</li> <li>B</li> </ul>
const ul = document.querySelector('ul');

// Node version — includes whitespace text nodes!
ul.firstChild;          // #text (the whitespace before first <li>)
ul.childNodes.length;   // 5 (text, li, text, li, text)

// Element version — only elements, what you actually want
ul.firstElementChild;   // <li>A</li>
ul.children.length;      // 2 (li, li)

Rule of thumb: Always use the Element versions unless you have a specific reason to work with text nodes or comments. This will save you hours of debugging.

⏸️ Check Your Understanding: Siblings & Nodes vs Elements

Before moving forward, can you answer these?

  1. How do you get the element immediately after <main> in the DOM?
  2. Why should you always prefer the "Element" versions of traversal properties?
  3. There is no built-in "siblings" property. How can you get all siblings of an element?
Check Your Answers
  1. Use mainEl.nextElementSibling. This skips text nodes and returns the next element, which would be <footer> in our example HTML.
  2. The plain Node versions (nextSibling, firstChild, etc.) include whitespace text nodes and comments, which are almost never what you want. The Element versions (nextElementSibling, firstElementChild, etc.) skip those and give you only HTML elements.
  3. Go up to the parent and then back down: Array.from(element.parentElement.children).filter(el => el !== element). This gives you all children of the parent except the element itself.

How confident are you with this concept?

😕 Still confused | 🤔 Getting there | 😊 Got it! | 🎉 Could explain it to a friend!

Combining Traversal Methods

The real power of traversal is chaining multiple steps together to reach any element from any starting point:

const section = document.querySelector('section');

// From section, get the first link in the header
section
  .parentElement            // <main>
  .previousElementSibling   // <header>
  .querySelector('a');       // first <a> in header

// From article, get the h2 inside its sibling section
const article = document.querySelector('article');
article
  .previousElementSibling   // <section>
  .firstElementChild;       // <h2>Welcome</h2>

Traversal vs querySelector — When to Use Which

Use traversal when…Use querySelector when…
You already have a reference to a nearby elementYou need to find an element anywhere in the page
You’re inside an event handler and need the parent card/rowYou’re setting up the page and need initial references
The relationship is structural (parent, next sibling)The target has a unique ID or class
You want to avoid searching the whole document againThe elements aren’t in a predictable structural relationship

Real-World Example: Accordion Component

Here is a common pattern where DOM traversal shines — an accordion (FAQ section) where clicking a question reveals its answer:

/* HTML structure */
// <div class="accordion">
//   <div class="accordion-item">
//     <button class="accordion-header">Question 1</button>
//     <div class="accordion-body">Answer 1</div>
//   </div>
//   <div class="accordion-item">
//     <button class="accordion-header">Question 2</button>
//     <div class="accordion-body">Answer 2</div>
//   </div>
// </div>

const accordion = document.querySelector('.accordion');

accordion.addEventListener('click', (event) => {
  // Use closest() to find the header that was clicked
  const header = event.target.closest('.accordion-header');
  if (!header) return;  // Click wasn't on a header

  // Traverse to the answer (next sibling of the button)
  const body = header.nextElementSibling;

  // Toggle visibility
  body.classList.toggle('is-open');

  // Close all other accordion items
  const allBodies = accordion.querySelectorAll('.accordion-body');
  allBodies.forEach(b => {
    if (b !== body) b.classList.remove('is-open');
  });
});

Notice how we used closest() to go up from the click target, nextElementSibling to go sideways to the answer panel, and querySelectorAll to find all siblings. This is DOM traversal in action.

⏸️ Check Your Understanding: Combining Methods

Before moving forward, can you answer these?

  1. In an event handler, how can you find the nearest parent with a specific class?
  2. When should you use DOM traversal instead of querySelector?
Check Your Answers
  1. Use event.target.closest(".class-name"). closest() walks up the tree and returns the first ancestor (or the element itself) that matches the CSS selector. It returns null if no match is found.
  2. Use traversal when you already have a reference to a nearby element and the relationship is structural (parent, sibling, child). Use querySelector when you need to find an element by ID, class, or selector from scratch.

How confident are you with this concept?

😕 Still confused | 🤔 Getting there | 😊 Got it! | 🎉 Could explain it to a friend!

Guided Practice: Navigate a Product Page

Traverse a Product Card Layout

You have a product listing page. Use DOM traversal to update elements without querying from document each time.

Create the product listing page

Create a new file called product-listing.html with this structure:




  
  Product Listing
  


  

Our Products

Widget A

$19.99

Widget B

$29.99

Widget C

$39.99

Click a Details button
💡 Need a hint?
This creates three product cards, each with a title, price, and button.

Use closest() to find the clicked card

Create app.js. Add a single click listener on the .products container (event delegation). When a button is clicked, use closest() to find the parent card:

const products = document.querySelector('.products');
const infoPanel = document.querySelector('.info-panel');

products.addEventListener('click', (event) => {
  const button = event.target.closest('button');
  if (!button) return;

  const card = button.closest('.product-card');
  console.log('Clicked card:', card);
});
💡 Need a hint?
closest() searches up the DOM tree from the clicked element. If the click was not on a button, we return early.

Traverse to get the product title and price

Inside the click handler, use traversal to get the product name and price from the card:

// The card's first child element is the h3
const title = card.firstElementChild.textContent;

// The price is the next sibling of the h3
const price = card.firstElementChild.nextElementSibling.textContent;

infoPanel.textContent = `Selected: ${title} — ${price}`;

No querySelector needed — we walked from the card to its children using traversal.

💡 Need a hint?
firstElementChild gives the h3, and nextElementSibling from the h3 gives the price paragraph.

Highlight sibling cards

When a card is clicked, add a highlight class to all its sibling cards (but not itself):

// Clear previous highlights
Array.from(card.parentElement.children).forEach(sibling => {
  sibling.classList.remove('highlight');
});

// Highlight the clicked card
card.classList.add('highlight');

We went up to the parent (.products), then down to all its children, then decided which ones to highlight.

💡 Need a hint?
parentElement.children gives you all siblings including the clicked card. We clear them all first, then add the highlight to just the clicked one.

Verify the traversal chain in DevTools

Open DevTools and add a breakpoint inside the click handler. Click a Details button and inspect each traversal step:

  • event.target — the button
  • button.closest('.product-card') — the card
  • card.firstElementChild — the h3
  • card.parentElement.children — all three cards

Confirm that no document.querySelector calls were used in the traversal logic.

💡 Need a hint?
In the Console, you can type these expressions directly to see what they return.

You're on track if you can:

  • ☐ You can reach the product title from a button click using traversal
  • ☐ You can highlight all sibling products of a clicked product
  • ☐ You use closest() to find a parent container from a nested click target
  • ☐ You understand when to use traversal vs querySelector

Independent Challenge: DOM Navigator

💪 DOM Navigator: Build an Interactive Tree Explorer

Now try this on your own without hints!

Your Task:

Build a small interactive page that lets the user click any element and see its DOM relationships displayed in a panel. When an element is clicked, highlight its parent (red outline), children (green outline), and siblings (blue outline) using DOM traversal properties — no document.querySelector calls allowed for the highlighting logic.
Requirements:
  • Create an HTML page with at least 3 levels of nesting (e.g. body → main → section → p)
  • When any element is clicked, display its tagName, parentElement, number of children, and number of siblings in an info panel
  • Highlight the clicked element’s parent with a red outline
  • Highlight all children with a green outline
  • Highlight all siblings (excluding the clicked element) with a blue outline
  • Use only DOM traversal properties (parentElement, children, nextElementSibling, etc.) — no querySelector in the highlight logic
  • Clear previous highlights when a new element is clicked
Stretch Goals (Optional):
  • Add a breadcrumb trail showing the path from <html> down to the clicked element using a while loop with parentElement
  • Let the user navigate with keyboard arrows: Up = parentElement, Down = firstElementChild, Left = previousElementSibling, Right = nextElementSibling
  • Show the difference between childNodes and children count for each clicked element

Success Criteria:

CriteriaYou've succeeded if...
Clicking highlights parent, children, and siblings correctlyMeets expectations
Info panel shows accurate DOM relationship dataMeets expectations
Only traversal properties used in highlight logic (no querySelector)Meets expectations
Keyboard navigation or breadcrumb trail implementedExceeds expectations
Clean code with clear variable names and commentsExceeds expectations

Summary

🏁 Lesson Complete: You Can Navigate the DOM

Key Takeaways:

  • The DOM is a tree of nodes with parent, child, and sibling relationships
  • Use parentElement to go up, children to go down, and nextElementSibling / previousElementSibling to go sideways
  • Always use the Element versions of traversal properties to skip invisible whitespace text nodes
  • closest() is the most powerful traversal method — it jumps straight to the nearest matching ancestor
  • DOM traversal lets you write shorter, faster code when you already have a reference to a nearby element

Learning Objectives Review:

Look back at what you set out to learn. Can you now:

  • ✅ Explain the parent–child–sibling relationships in the DOM tree Check!
  • ✅ Use parentNode and parentElement to move up the tree Got it!
  • ✅ Use children, firstElementChild, and lastElementChild to move down Can explain it!
  • ✅ Use nextElementSibling and previousElementSibling to move sideways Could teach this!
  • ✅ Explain the difference between Node properties and Element properties Check!
  • ✅ Combine traversal methods to reach any element from any starting point Got it!
  • ✅ Apply DOM traversal in a real-world scenario Can explain it!

If you can confidently answer "yes" to most of these, you're ready to move on!

Think & Reflect:

💭 💭 Reflection Questions

  • Why do you think the DOM has both Node and Element versions of traversal properties?
  • In what situations would traversal be more readable than querySelector?
  • How does closest() simplify event handling compared to chaining parentElement?
  • Can you think of a UI pattern in a website you use daily where DOM traversal would be used behind the scenes?

🤔 Real-World Test:

DOM traversal is everywhere in professional front-end development. UI component libraries like accordions, tab panels, dropdown menus, and data tables all rely heavily on traversal to connect related elements. When a user clicks a tab header, the code uses nextElementSibling to find the panel to show. When a row in a table is deleted, the code uses parentElement to find the table body and children to recount the rows.

Modern frameworks like Vue and React abstract some of this away, but understanding traversal helps you debug layout issues, write custom components, and work with third-party libraries that manipulate the DOM directly.

🎯 Looking Ahead:

Now that you can navigate the DOM tree, you’re ready to learn how to listen for user interactions. In the Event Handling tutorial, you’ll learn about click events, keyboard events, and event delegation — and you’ll see how traversal and events work together to build interactive web pages.

Recommended Next Steps

Continue Learning

Ready to move forward? Continue with the next tutorial in this series:

Dynamic Content

Related Topics

Explore these related tutorials to expand your knowledge:

Additional Resources

Deepen your understanding with these helpful resources:

Progress tracking is disabled. Enable it in to track your completed tutorials.