In the Document Object Model (DOM), child elements are the nodes nested directly within a parent element. Accessing these children is a fundamental task for building dynamic user interfaces, such as generating menu items from data, validating form fields, or creating interactive galleries.
JavaScript offers two primary ways to access children: one that includes every node (whitespace, comments, and tags) and one that targets only HTML Elements.
1. Accessing HTML Elements: children
The children property is the most commonly used tool for UI development. It returns an HTMLCollection containing only the actual HTML element nodes (e.g., <div>, <li>, <span>), completely ignoring whitespace, line breaks, and comments.
const parent = document.querySelector('#parent-list');
const childElements = parent.children;
console.log(childElements.length); // Total number of tags inside
console.log(childElements[0]); // Access the first tag
2. Accessing All Nodes: childNodes
The childNodes property returns a NodeList that includes everything inside the element. This includes element nodes, text nodes (even empty spaces/tabs between tags), and comment nodes.
const parent = document.querySelector('#parent-list');
const allNodes = parent.childNodes;
// In a standard HTML file, allNodes[0] is often a Text node (whitespace)
3. firstElementChild and lastElementChild
The firstElementChild property returns the first child element of the specified node. If the element has no children, or if all its children are non-element nodes (like text or comments), it returns null.
Example: Selecting the first <li>
const list = document.querySelector('ul');
// Even if there is a line break after <ul>, this skips it
const firstItem = list.firstElementChild;
if (firstItem) {
firstItem.style.fontWeight = 'bold';
firstItem.textContent = "I am the first tag!";
}
The lastElementChild property returns the final child element within a parent. It is incredibly useful for tasks like adding a “Delete” button to the most recently added item in a list or finding the footer of a dynamic card.
Example: Removing the last item
const container = document.getElementById('wrapper');
// Finds the last actual HTML tag inside the wrapper
const lastBox = container.lastElementChild;
if (lastBox) {
container.removeChild(lastBox);
}
4. firstChild andlastChild
The firstChild property returns the very first node inside an element, regardless of its type. In a perfectly minified HTML document where there is no space between tags, firstChild will likely be an Element node. However, in human-readable, indented HTML, it is almost always a Text node.
The Whitespace Phenomenon
Browsers treat the carriage return and indentation between an opening tag and its first child as a literal text string.
// HTML Structure:
// <div id="container">
// <p>Hello World</p>
// </div>
const container = document.getElementById('container');
console.log(container.firstChild.nodeType); // 3 (Text Node)
console.log(container.firstChild.nodeValue); // "\n " (The newline and spaces)
If you were to change the HTML to <div id="container"><p>Hello</p></div>, then firstChild would correctly point to the <p> element (Node Type 1).
Similarly, lastChild returns the final node within a parent. This property is frequently affected by the whitespace between the last child element and the parent’s closing tag.
// HTML Structure:
// <ul>
// <li>Item 1</li>
// <li>Item 2</li>
// </ul>
const list = document.querySelector('ul');
console.log(list.lastChild.nodeType); // 3 (Text Node)
5. Specific Selection: querySelector & querySelectorAll
For complex structures, you may want to skip the immediate level and find specific children or grandchildren using CSS selectors.
const parent = document.querySelector('.container');
// Finds only the direct children that are buttons
const directButtons = parent.querySelectorAll(':scope > button');
// Finds the first child with the class "active"
const activeChild = parent.querySelector('.active');
6. Practical Use Cases and Iteration
Iterating through Children
Because children returns an HTMLCollection, the modern approach is to use the spread operator or Array.from() to unlock functional programming methods.
const menu = document.querySelector('#nav-menu');
// Convert to array to use .forEach
Array.from(menu.children).forEach((child, index) => {
child.style.transitionDelay = `${index * 100}ms`;
});
Checking for Children
To determine if an element has any content at all, you can use the hasChildNodes() method or check the .length of the children property.
if (parent.hasChildNodes()) {
console.log("This element is not empty.");
}
if (parent.children.length === 0) {
console.log("This element has no HTML tags inside.");
}
