In JavaScript, navigating “up” the DOM tree to find an element’s parent is a common task, especially when handling events or building dynamic components. There are two primary properties and one powerful method used for this purpose.

1. parentElement vs. parentNode

For the vast majority of web development tasks, these two properties are nearly identical, but they have a distinct technical boundary.

parentElement

This property returns the parent of the specified node only if it is an Element object (an HTML tag). If the parent is not an element (for example, the parent of the <html> tag is the document), it returns null.

JavaScript
const child = document.querySelector('.child');
const parent = child.parentElement;

if (parent) {
  parent.style.border = '1px solid red';
}

parentNode

This is a more generic property from the older Node API. It returns any parent, including the Document node or DocumentFragment.

  • The Key Difference: document.documentElement.parentNode returns the document object, while document.documentElement.parentElement returns null.
[!TIP] Which should I use? In 99% of cases, use parentElement. You are usually looking for a container to style or manipulate, and you don’t want your code to accidentally try to modify the document object.

2. closest(): Finding Specific Ancestors

One of the most powerful additions to modern JavaScript is the .closest() method. Instead of manually climbing the tree with parentElement.parentElement, you can look for the first ancestor that matches a specific CSS selector.

Syntax

JavaScript
const ancestor = element.closest(selector);

Why it’s useful:

  • Event Delegation: If a user clicks a button inside a complex card, you can easily find the card element.
  • Self-Inclusion: The method starts by checking the element itself. If the element matches the selector, it returns the element. If not, it moves to the parent, and so on.
JavaScript
document.addEventListener('click', (event) => {
  // Find the nearest list item from the click target
  const listItem = event.target.closest('li');
  
  if (listItem) {
    console.log('You clicked inside list item:', listItem.id);
  }
});

3. Traversing Multiple Levels

If you must traverse without a selector, you can chain the properties, though this is generally discouraged for maintainability.

JavaScript
// Moves up two levels
const grandParent = child.parentElement.parentElement;

[!TIP] Always check for existence when chaining: child.parentElement?.parentElement. If the first parent is null, the script will throw an error without the optional chaining ?. operator.

4. Technical Nuance: Shadow DOM

When working with Web Components and the Shadow DOM, parentElement will return null if you reach the shadow root. To jump from a shadow root back to the “light DOM” host, you must use the assignedSlot property or navigate via the host property of the shadow root.

JavaScript

// Inside a Shadow DOM context
const hostElement = myShadowElement.getRootNode().host;

5. Practical Use Case: Event Delegation

Getting the parent is essential when you want to handle a click on a button but perform an action on the entire row or container that holds that button.

JavaScript
document.addEventListener('click', (event) => {
    if (event.target.classList.contains('delete-btn')) {
        // Find the list item containing this button and remove it
        const listItem = event.target.closest('li');
        listItem.remove();
    }
});

Categorized in:

JS DOM,