Computer Programming 1 (JavaScript) • Web Basics (DOM & Events)

Selecting DOM Elements — I can select elements with querySelector/querySelectorAll in supported tasks.

Learn to pinpoint any HTML element on a page so your JavaScript code can read, change, or animate it.

A JavaScript data file with code brackets and a laptop.
Inside this lesson 10 sections 5 questions 7 figures

SECTION 1

Historical Context & Motivation

In the early days of the web, HTML pages were completely static — once the browser loaded a page, nothing could change. Developers dreamed of pages that could respond to a user's click, update text in real time, or highlight an important paragraph. To make that happen, JavaScript needed a way to reach into the page and grab specific elements. The story of how we got the tools we use today is a journey through competing browsers, messy workarounds, and eventual standardization.

1995 • JavaScript Is Born

Brendan Eich creates JavaScript at Netscape in just ten days. Early scripts could only access a few hard-coded collections like document.forms and document.images — selecting an arbitrary element was impossible.

1998 • DOM Level 1 Standard

The W3C publishes the first official DOM specification. It introduces getElementById(), letting developers select one element by its unique ID — a huge step forward.

2006 • jQuery Simplifies Selection

The jQuery library introduces a single $() function that accepts any CSS selector. It becomes wildly popular because native DOM methods are still limited and verbose.

2013 • querySelector Gains Full Support

All major browsers fully support querySelector() and querySelectorAll(), which were standardized in the Selectors API. Developers can now use CSS selectors natively — no library required.

The central question these tools answer is simple: How do I tell JavaScript exactly which HTML element I want to work with? Today, querySelector and querySelectorAll are the modern, go-to methods for answering that question. They accept the same CSS selector syntax you already know from stylesheets, making them intuitive and powerful.

SECTION 2

Core Principles & Definitions

Before diving into code, you need to understand a few foundational ideas. The Document Object Model (DOM) is a tree-like representation of your HTML page that the browser builds in memory. Each HTML tag — every <div>, <p>, or <button> — becomes a node in that tree. JavaScript uses the DOM as a bridge to read or modify the page.

1

CSS Selectors as Addresses

A CSS selector is a pattern string like "#main", ".card", or "h2". It tells the browser which element(s) you mean, just like a street address identifies a building.
2

querySelector — First Match

document.querySelector(selector) scans the DOM from top to bottom and returns the first element that matches. If nothing matches, it returns null.
3

querySelectorAll — All Matches

document.querySelectorAll(selector) returns a NodeList containing every element that matches. A NodeList looks like an array but is not quite one — though you can loop through it with forEach.
4

Selector Types You Already Know

ID selectors use #, class selectors use ., and tag selectors use the element name directly. You can combine them just like in a CSS stylesheet: "div.card > h2".
✦

KEY TAKEAWAY

Think of the DOM as a giant filing cabinet and querySelector as your hand reaching in to pull out one specific folder. If you need every folder with the same label, use querySelectorAll — it pulls out all matching folders at once and stacks them in a neat pile (a NodeList) for you to go through one by one.

SECTION 3

Visual Explanation — The DOM Tree

The diagram below shows a small HTML page rendered as a DOM tree. When you call document.querySelector(".highlight"), the browser walks the tree from the top (document) downward, checking each node until it finds the first element with class="highlight". That element is returned to your JavaScript code.

DOM Tree — querySelector(".highlight") finds the first matchdocument<html><head><body><h1 id="title"><ul><li class="highlight">← FIRST MATCH ✓<li>Cyan glow = element returned by querySelector(".highlight")MatchOther nodes
The browser starts at document (top) and walks downward through <html>, <body>, and into <ul> until it hits the first <li class="highlight"> — that element (highlighted in cyan) is returned.

Notice that the second <li> node in the tree does not have the highlight class, so it is skipped. If you used querySelectorAll("li") instead, both list items would be collected into a NodeList. The key difference is singular (first match) versus plural (all matches).

SECTION 4

How It Works — Syntax & Return Values

Both methods live on the document object (though you can also call them on any element to search only within that element's children). Understanding their exact syntax and return values is essential for writing bug-free code.

querySelector Syntax

QUERYSELECTOR

constelement=document.querySelector("cssSelector");const element = document.querySelector("cssSelector");

cssSelector — any valid CSS selector string (e.g., "#nav", ".btn", "div > p"). Returns the first matching Element or null if no match is found.

querySelectorAll Syntax

QUERYSELECTORALL

constnodeList=document.querySelectorAll("cssSelector");const nodeList = document.querySelectorAll("cssSelector");

Returns a static NodeList of all matching elements. "Static" means the list is a snapshot — if the DOM changes later, the NodeList does not update automatically.

Common Selector Patterns

SelectorWhat It MatchesExample
"#hero"Element with id="hero"querySelector("#hero")
".card"All elements with class="card"querySelectorAll(".card")
"p"All <p> elementsquerySelectorAll("p")
"ul > li.active"Direct <li> children of <ul> with class="active"querySelectorAll("ul > li.active")
"input[type='email']"Input elements with type attribute set to emailquerySelector("input[type='email']")

Frequently used CSS selector patterns with querySelector and querySelectorAll

⚠

Null Check Tip

Because querySelector returns null when no match is found, always guard your code: if (el !== null) { /* safe to use el */ }. Trying to read a property on null causes a "Cannot read properties of null" error — one of the most common bugs for beginners.

SECTION 5

Detailed Breakdown — Selector Types & Scoping

Understanding the different categories of CSS selectors will help you choose the right one for each task. Selectors range from simple (a single tag name) to complex (multiple combinators and pseudo-classes). The diagram below groups the most useful selectors by category, along with their relative specificity from least to most targeted.

CSS Selector Categories for querySelector / querySelectorAllTAG SELECTORS"p""div""h1"Selects all elements ofthat tag type.CLASS SELECTORS".card"".btn-primary"".hidden"Selects elements with aspecific class attribute.ID SELECTORS"#navbar""#main-content""#footer"Selects the unique elementwith that id. Should be one.COMBINATORS & ADVANCEDDESCENDANT / CHILD"div p" (descendant)"ul > li" (direct child)Narrows by relationship.ATTRIBUTE"[type='text']""[data-id]"Matches attribute values.PSEUDO-CLASSES"li:first-child""p:nth-child(2)"Matches position / state.Broad (many matches)Specific (few matches)tagclassidSpecificity Spectrum →
Selector categories organized from broadest (tag selectors) to most targeted (ID selectors). Combinators and pseudo-classes add precision within any category. The spectrum arrow at the bottom illustrates increasing specificity from left to right.

Scoped Selections

You are not limited to searching the entire document. Any element can be the starting point for a search. For example, if you have a reference to a <section> element stored in a variable called section, you can write section.querySelector(".title") to find the first .title element only inside that section. This is called a scoped selection, and it keeps your code precise — especially on pages with many repeated components.

SECTION 6

Worked Example — Building an Interactive To-Do List

Imagine you have a simple to-do list in HTML and you want JavaScript to mark a task as complete when the user clicks a button. Walk through each step to see how querySelector and querySelectorAll fit into the workflow.

ℹ

Starting HTML

Assume this HTML is on the page: <ul id="tasks"> <li class="task">Study JavaScript</li> <li class="task">Read chapter 4</li> <li class="task done">Install VS Code</li> </ul> <button id="clear-btn">Clear completed</button>

Selecting & Modifying DOM Elements

  1. 1

    Step 1 — Select the task list container

    We need a reference to the <ul> so we can work with its children later. Because the list has a unique id, an ID selector is perfect.
    const taskList = document.querySelector("#tasks"); — returns the single <ul> element.
  2. 2

    Step 2 — Select all task items

    There are three <li> elements sharing the class task. We want all of them, so we use querySelectorAll.
    const allTasks = document.querySelectorAll(".task"); — returns a NodeList with 3 items.
  3. 3

    Step 3 — Loop through and log each task

    A NodeList supports forEach, so we can iterate just like with an array. Inside the loop, task.textContent gives us the visible text of each list item.
    allTasks.forEach(task => console.log(task.textContent)); — prints "Study JavaScript", "Read chapter 4", "Install VS Code".
  4. 4

    Step 4 — Select the clear button and add a click handler

    We grab the button by its ID, then attach an event listener. Inside the listener, we select only the completed tasks using a compound selector ".task.done" (elements that have both classes).
    const clearBtn = document.querySelector("#clear-btn"); clearBtn.addEventListener("click", () => { const done = document.querySelectorAll(".task.done"); done.forEach(item => item.remove()); }); — clicking the button removes the "Install VS Code" item from the page.
  5. 5

    Step 5 — Verify with a scoped selection

    After removal, we can confirm how many tasks remain by using a scoped search on the list container we saved in Step 1.
    console.log(taskList.querySelectorAll(".task").length); — outputs 2, confirming the completed task was removed.

SECTION 7

querySelector vs. Older DOM Methods

You may still see older selection methods in tutorials and codebases. It helps to know how querySelector and querySelectorAll compare so you can pick the right tool and read legacy code without confusion.

MethodReturnsAccepts CSS Selectors?Best For
getElementById()Single elementNo — ID string only (no #)Fastest lookup for a known ID
getElementsByClassName()Live HTMLCollectionNo — class name only (no .)Rare; live collection can cause surprises
getElementsByTagName()Live HTMLCollectionNo — tag name onlyRare in modern code
querySelector()Single element or nullYes — any CSS selectorModern default for one element
querySelectorAll()Static NodeListYes — any CSS selectorModern default for multiple elements

Comparison of DOM selection methods

🔄

LIVE vs. STATIC

A live collection is like a search engine that re-runs itself every time you look at the results — add or remove elements and the list changes instantly. A static NodeList (from querySelectorAll) is like printing that search result onto paper — it won't change even if the page does. Static lists are usually easier to reason about and avoid unexpected bugs.

SECTION 8

Connection to Advanced DOM Techniques

Once you are comfortable with querySelector and querySelectorAll, you will be ready to explore more advanced DOM patterns. The table below previews how these foundational skills connect to topics you will encounter in later courses and projects.

What You Know NowWhere It Leads
Selecting elements and reading their textDOM traversal — walking the tree with parentNode, children, nextElementSibling
Adding event listeners to selected elementsEvent delegation — attaching one listener to a parent instead of many to children
Looping through a NodeList with forEachHigher-order iteration — spreading into an array to use map, filter, and reduce
Changing element content with textContent or innerHTMLTemplate literals and frameworks (React, Vue) that manage DOM updates automatically
Selecting by CSS selectorsDynamic class toggling, CSS animations triggered by JavaScript

From basics to advanced DOM concepts

Frameworks like React and Vue abstract away much of the manual DOM selection you learn here, but understanding the underlying mechanism gives you a critical advantage. When a framework does something unexpected, knowing how querySelector works lets you debug at the browser level — a skill that separates effective developers from those who feel lost when things break.

SECTION 9

Practice Problems

Test your understanding with these five problems. They start with a straightforward concept check and build toward scenarios that require you to combine multiple ideas from the lesson.

Question 1: CONCEPTUAL

What is the key difference between querySelector and querySelectorAll? In what situation would choosing the wrong one cause a bug?
Show answer
querySelector returns only the first matching element (or null if there is no match), while querySelectorAll returns a NodeList of all matching elements. If a page has five .card elements and you use querySelector(".card"), you will only get the first one — the other four are silently ignored. Conversely, using querySelectorAll when you only need one element means you must access index 0 or use forEach unnecessarily.

Question 2: BASIC

Write a single line of JavaScript that selects the first <img> element inside a <div> with the class gallery and stores it in a variable called firstPhoto.
Show answer
The correct line is: const firstPhoto = document.querySelector("div.gallery img"); This uses a descendant combinator to say "find an <img> that is somewhere inside a <div> with class gallery." Because querySelector returns only the first match, we get exactly one element.

Question 3: INTERMEDIATE

Given this HTML: <ul id="menu"><li class="item active">Home</li><li class="item">About</li><li class="item active">Blog</li></ul>, write JavaScript that collects all li elements with the class active and removes the active class from each one.
Show answer
Use querySelectorAll with a compound selector and loop: const actives = document.querySelectorAll("#menu li.active"); actives.forEach(li => li.classList.remove("active")); The selector "#menu li.active" scopes the search to the specific <ul> and finds only list items that carry the active class. The classList.remove method strips the class without affecting other classes on the element.

Question 4: APPLIED

You are building a quiz app. Each question is inside a <div class="question"> and the answer options are <button class="option"> elements inside that div. When the user clicks an option, you want to add the class selected to the clicked button and remove selected from any other button in the same question. Describe the selection strategy and write the code.
Show answer
First, select all option buttons and add a click listener to each. Inside the listener, use closest(".question") or scope from the parent to find sibling options. Full code: document.querySelectorAll(".option").forEach(btn => { btn.addEventListener("click", () => { const questionDiv = btn.closest(".question"); questionDiv.querySelectorAll(".option").forEach(b => b.classList.remove("selected")); btn.classList.add("selected"); }); }); The key insight is using a scoped querySelectorAll on the parent question div to ensure you only clear selections within the same question, not across the entire page.

Question 5: CRITICAL THINKING

A classmate writes this code to hide all paragraphs on the page: document.querySelector("p").style.display = "none"; They are confused because only the first paragraph disappears. Explain what went wrong, fix the code, and discuss whether there is ever a performance reason to prefer getElementById over querySelector for selecting by ID.
Show answer
The bug is using querySelector (singular) instead of querySelectorAll (plural). The fix: document.querySelectorAll("p").forEach(p => p.style.display = "none"); As for performance, getElementById is marginally faster because the browser maintains an internal hash map of IDs, so the lookup is O(1). Meanwhile, querySelector("#myId") must parse the selector string first. In practice, this difference is negligible for almost all web apps — readability and consistency matter more. However, in performance-critical loops running thousands of times, getElementById could be preferred.

SUMMARY

Lesson Summary

Summary

The Document Object Model (DOM) is a tree-shaped representation of every HTML element on the page. JavaScript uses querySelector to grab the first matching element and querySelectorAll to collect all matching elements into a static NodeList. Both accept any valid CSS selector — IDs with #, classes with ., tags by name, and complex combinators like "ul > li.active".

Always remember that querySelector returns null when nothing matches, so check before using the result. You can call these methods on any element — not just document — to perform scoped selections within a specific part of the page. Compared to older methods like getElementById and getElementsByClassName, querySelector and querySelectorAll are more flexible, more consistent, and the recommended approach for modern JavaScript development.

Varsity Tutors • Computer Programming 1 (JavaScript) • Selecting DOM Elements — querySelector / querySelectorAll