How to Use JS querySelector and querySelectorAll

Learn to select DOM elements with any CSS selector using querySelector and querySelectorAll. Covers return types, NodeList iteration, scoping, and common mistakes.

5 min read

The querySelector and querySelectorAll methods are the most flexible way to select DOM elements in JavaScript. Instead of being limited to IDs or class names like the older selection methods, they accept any valid CSS selector.

If you know how to target elements in a stylesheet, you already know the syntax these methods need.

javascriptjavascript
const heading = document.querySelector("h1");
const intro = document.querySelector(".intro");
const button = document.querySelector("#submit-btn");
const emailInput = document.querySelector('input[type="email"]');
const allParagraphs = document.querySelectorAll("section p");

The first four lines each find a single element using a different kind of CSS selector: tag, class, ID, and attribute. The last line finds every paragraph inside a section element.

The power of these methods is in the selector string. Anything you can write in a CSS stylesheet, you can pass to them.

Notice the naming pattern: querySelector returns one element, and querySelectorAll returns many. The singular and plural in the names tell you what to expect. This is the most important distinction to keep straight.

Syntax and Return Values

Before using these methods in real code, it helps to know exactly what each one hands back. This section covers the calling pattern and the return value for both a match and a no-match case.

Both methods follow the same calling pattern, taking a CSS selector string as their single argument:

javascriptjavascript
const firstMatch = document.querySelector(cssSelector);
const allMatches = document.querySelectorAll(cssSelector);

The difference is entirely in what comes back. Here is what each returns and what you get when nothing matches:

MethodReturns on matchReturns on no match
querySelectorThe first matching elementnull
querySelectorAllA static NodeListEmpty NodeList (length 0)

This distinction matters every time you call these methods. The null return from querySelector means you need an if-check before using the result.

The always-a-NodeList return from querySelectorAll means you check the length property instead. Both patterns are simple once you internalize them.

Getting the First Match with querySelector

Use querySelector when you need one specific element. The method searches the entire document and stops at the very first element that matches:

javascriptjavascript
const activeTab = document.querySelector(".tab.active");
const submitInput = document.querySelector('input[type="submit"]');

Even if ten elements have the class "tab," the first line only returns the one that also has the class "active," and only the first such element in document order. If you need all ten tab elements, you need querySelectorAll instead. The most important habit with querySelector is guarding against null, because if the element is not on the page, the method gives you nothing:

javascriptjavascript
const modal = document.querySelector("#modal");
 
if (modal) {
  modal.classList.add("open");
}

Without this guard, calling a method on null throws a TypeError that stops your script. The guard is one line and prevents an entire category of runtime errors.

Getting Every Match with querySelectorAll

Use querySelectorAll when you need to work with a group of elements: all links, all images, all items in a navigation menu.

javascriptjavascript
const allLinks = document.querySelectorAll("a");
const navItems = document.querySelectorAll("nav ul > li");
console.log(allLinks.length);

The returned NodeList is array-like. You can read its length, access elements by index with square brackets, and iterate with forEach:

javascriptjavascript
const cards = document.querySelectorAll(".card");
 
cards.forEach((card, index) => {
  card.style.setProperty("--index", index);
});
 
const firstCard = cards[0];

The forEach call runs once per card, and the index lets you tag each one. Square-bracket indexing works just like an array, so cards[0] gives you the first matched element directly.

javascriptjavascript
if (cards.length === 0) {
  console.log("No cards found");
}

Checking length before acting on a NodeList avoids running logic against an empty result, which matters when the selector might not match anything on a given page.

The most important detail about querySelectorAll is that the NodeList is static. It is a snapshot of what matched at the exact moment you called the method. If you later add a new element with the class "card" to the page, the cards NodeList from above will not include it.

javascriptjavascript
const items = document.querySelectorAll(".item");
console.log(items.length);  // Suppose it is 3
 
const newDiv = document.createElement("div");
newDiv.className = "item";
document.body.appendChild(newDiv);
 
console.log(items.length);  // Still 3 -- the snapshot did not update

This static behavior is usually what you want. It means your loop will not suddenly get longer while you are iterating. If you need a collection that stays in sync with the page as elements are added and removed, use getElementsByClassName or getElementsByTagName instead.

Searching Inside a Parent Element

Both methods work on any element, not just the document. When called on a parent element, the search is limited to that element's descendants.

This is called scoping.

Here is a page with content in two different sections:

htmlhtml
<main id="content">
  <p>Inside main</p>
</main>
<footer>
  <p>Inside footer</p>
</footer>

Both sections contain a paragraph, but you only want the one inside the main element. Scoping the querySelectorAll call to the main element excludes the footer's paragraph:

javascriptjavascript
const main = document.querySelector("#content");
const mainParagraphs = main.querySelectorAll("p");
console.log(mainParagraphs.length);

The output is 1. The paragraph inside the footer is excluded because the search was scoped to the main element.

Scoping like this is cleaner and often faster than writing a longer, more complex selector on the document. It is especially useful in component-based code where the same class names appear in different parts of the page.

CSS Selectors You Can Use

Both methods accept any selector you would write in a stylesheet. Here are the most useful categories for JavaScript work:

Selector typeExample stringWhat it finds
Tag"p"All paragraph elements
Class".card"All elements with class "card"
ID"#header"The element with id "header"
Attribute"[data-id]"All elements with a data-id attribute
Descendant"article p"Paragraphs inside articles
Direct child"ul > li"li elements directly inside a ul
Pseudo-class"li:first-child"First li of any parent
Negation"li:not(.active)"li elements without the active class
Multiple"h1, h2, h3"All heading elements

A NodeList has forEach but not map, filter, or reduce. When you need those array methods, convert the NodeList to a real array:

javascriptjavascript
const prices = document.querySelectorAll(".price");
const highPrices = Array.from(prices).filter((el) => {
  return Number(el.textContent.replace("$", "")) > 50;
});

Array.from and spread syntax both do the conversion. Use whichever reads more clearly in context.

Common Mistakes

Three mistakes come up repeatedly with these methods. First, forgetting the CSS prefix for classes and IDs.

The string "submit-btn" looks for a submit-btn HTML tag, not an element with that ID. You need "#submit-btn" for an ID and ".submit-btn" for a class.

Second, calling array methods like map or filter directly on a NodeList. NodeList supports forEach but not the full array API. Convert first with Array.from or spread syntax.

Third, forgetting the null check after querySelector. The method returns null when nothing matches, and accessing a property on null throws a TypeError. The guard pattern shown earlier is not optional: check for null, then use the element.

querySelector vs getElementById for ID Selection

Both methods can select an element by its ID, but they are not interchangeable defaults. Here is the difference that matters most:

querySelectorgetElementById
Selector"#hero""hero" (no hash needed)
SpeedSlightly slower (parses CSS)Faster (direct lookup)

Use getElementById when you are selecting by ID. It is faster and the name tells the reader exactly what is happening.

Use querySelector when you need any other kind of CSS selector. For an overview of all five DOM selection methods, see the complete guide to selecting DOM elements.

Where to Go Next

Now that you can select elements, learn how to change text content and how to change CSS styles on the elements you find.

Rune AI

Rune AI

Key Insights

  • querySelector returns the first matching element or null.
  • querySelectorAll returns a static NodeList of all matches.
  • Both accept any valid CSS selector: tags, classes, IDs, attributes, and combinators.
  • The NodeList is static; it does not update when the DOM changes.
  • Always check for null or empty length before using the result.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between querySelector and querySelectorAll?

querySelector returns the first element that matches the CSS selector, or null if nothing matches. querySelectorAll returns a static NodeList of all matching elements, which may be empty but is never null.

Can I use pseudo-classes like :hover or :focus?

Partially. These methods accept static pseudo-classes like :first-child, :last-child, :nth-child, and :not. They do not match dynamic states like :hover or :focus because those depend on user interaction, not the current DOM state.

Is the NodeList from querySelectorAll an array?

No. It is a NodeList, which is array-like with .length and indexing. It supports forEach but not map, filter, or reduce. Convert to an array with Array.from() or spread syntax when you need those methods.

Conclusion

querySelector and querySelectorAll are the most versatile DOM selection methods in JavaScript. Their power comes from accepting any CSS selector, which means you can reuse the selector knowledge you already have from writing CSS. Use querySelector for the first match, querySelectorAll for every match, and always check for null or empty before using the result.