What you'll be able to do
By the end of this page you should be able to start from one element you already have and walk up to a parent, down to children, or sideways to siblings — without writing a ten-level CSS selector.
Previous post: DOM select and change.
Who this is for
- People who copy
.parent.parent.querySelector(...)and hate maintaining it - Beginners who only know
querySelectorfrom the document root - Anyone building “click the row, highlight the whole card” UI
You can skip this if traversal properties already feel obvious. Come back when your selector string is longer than your function.
Parent: parentElement
const button = document.querySelector("button");
const card = button.parentElement;
Scenario A — button inside a card. Start at the button; the card is one step up.
Scenario B — null at the top. document.documentElement.parentElement is null — there is no parent above the root.
Prefer parentElement over parentNode as a beginner; both exist, parentElement skips non-element nodes cleanly in most cases.
Children: children and firstElementChild
const list = document.querySelector("ul");
console.log(list.children.length);
console.log(list.firstElementChild);
console.log(list.lastElementChild);
Scenario A — count items in a list without querySelectorAll("li").length if you already hold the ul.
Scenario B — walk children in a loop:
for (const item of list.children) {
console.log(item.textContent);
}
children is HTMLCollection (live). childNodes includes text nodes — usually noisier for beginners.
Siblings: nextElementSibling / previousElementSibling
const current = document.querySelector(".step.active");
const next = current.nextElementSibling;
Scenario A — wizard steps. Move highlight to the next sibling block.
Scenario B — no sibling. Returns null at the end — check before using.
When querySelector is still fine
| Situation | Tool |
|---|---|
| Known unique id/class from scratch | querySelector |
| Already inside an event target | traversal from event.target |
| Global search every time | short selector OK |
Scenario A — page load, one hero. querySelector(".hero") is clear.
Scenario B — click on a delete icon inside a row. Traverse to tr, not document.querySelector("#row-17").
A tiny practice file
Save as dom-walk.html.
<!DOCTYPE html>
<html>
<body>
<ul id="menu">
<li>Home<button class="info">?</button></li>
<li>About<button class="info">?</button></li>
</ul>
<pre id="out"></pre>
<script>
document.querySelectorAll(".info").forEach(function (btn) {
btn.addEventListener("click", function () {
const li = btn.parentElement;
document.getElementById("out").textContent =
"Row text: " + li.firstElementChild.textContent.trim();
});
});
</script>
</body>
</html>
Mistakes I see a lot
1. Assuming nextSibling is the next <li>. Text nodes sit between tags; use *ElementSibling.
2. Long chained selectors instead of one hop from a known node.
3. Not checking for null after traversal.
4. Mixing children with array methods without converting.
5. Traversing from document when you already have the clicked element.
What to try before the next post
- From a button, log its parent’s tag name.
- Loop
ul.childrenand log eachli. - Highlight
.activeand move tonextElementSibling. - Build
dom-walk.html.
Next in this series: create elements and lists — createElement and building UI without unsafe innerHTML habits.
Try this next outside the series
Browser JavaScript stops feeling isolated once you connect it to forms and component-oriented UI work.
- React forms without pain — see modern form handling after you understand the DOM version first
- HTML to JSX Converter — move tiny HTML snippets into React-style markup when you're ready