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.
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.
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:
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:
| Method | Returns on match | Returns on no match |
|---|---|---|
| querySelector | The first matching element | null |
| querySelectorAll | A static NodeList | Empty 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:
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:
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.
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:
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.
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.
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 updateThis 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:
<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:
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 type | Example string | What 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:
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:
| querySelector | getElementById | |
|---|---|---|
| Selector | "#hero" | "hero" (no hash needed) |
| Speed | Slightly 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
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.
Frequently Asked Questions
What is the difference between querySelector and querySelectorAll?
Can I use pseudo-classes like :hover or :focus?
Is the NodeList from querySelectorAll an array?
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.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.