Before you can change an element, you have to find it.
getElementById
<h1 id="title">Hello</h1>
<script>
const title = document.getElementById("title");
console.log(title.textContent); // → Hello
</script>
It takes the id without a #, and returns the element or null. It is fast and simple.
querySelector
querySelector takes any CSS selector, the same ones you use in the CSS course, and returns the first match (or null).
<ul class="menu">
<li class="active">Home</li>
<li>Shop</li>
</ul>
<script>
console.log(document.querySelector(".menu li").textContent); // → Home
console.log(document.querySelector("li.active").textContent); // → Home
console.log(document.querySelector("ul > li:last-child").textContent); // → Shop
console.log(document.querySelector(".nothing")); // → null
</script>
querySelectorAll
Returns all matches as a NodeList (empty if none).
<p class="item">One</p><p class="item">Two</p><p class="item">Three</p>
<script>
const items = document.querySelectorAll(".item");
console.log(items.length); // → 3
items.forEach(item => console.log(item.textContent));
for (const item of items) {
item.style.color = "tomato";
}
console.log(Array.from(items).map(i => i.textContent)); // → ["One", "Two", "Three"]
</script>
A NodeList has forEach and can be used with for...of, but not map or filter. Convert it with Array.from(items) or [...items] when you need those.
Searching inside an element
You can call these on any element, not just document.
<div id="a"><span class="x">in a</span></div>
<div id="b"><span class="x">in b</span></div>
<script>
const b = document.querySelector("#b");
console.log(b.querySelector(".x").textContent); // → in b
</script>
Always check for null
If nothing matches, querySelector returns null, and using it causes the famous error Cannot read properties of null.
const button = document.querySelector("#save");
if (button) {
button.disabled = true;
}
closest and matches
Two helpers you will use with events:
<ul id="list"><li><b id="inner">text</b></li></ul>
<script>
const inner = document.querySelector("#inner");
console.log(inner.closest("li").tagName); // → LI (nearest ancestor that matches)
console.log(inner.matches("b")); // → true (does this element match?)
</script>
Live vs static collections
Older methods like getElementsByClassName() return a live collection that updates when the page changes. querySelectorAll() returns a static snapshot. The static version is easier to reason about, which is why modern code prefers it.
Which one?
querySelector and querySelectorAll for everything, with getElementById as an optional shortcut. A selector like #id or .class reads the same as in CSS.