Selecting Elements
Before you can change what's on the page, you have to find it. The DOM offers a small, well-defined set of methods for selecting elements — by id, by class, by tag, by name, or by any CSS selector you can write. Each returns a slightly different thing, and a couple of them behave in ways that surprise beginners. This page covers them all.
The minimal HTML we'll select from
index.html
<main id="app">
<h1 class="title">Tasks</h1>
<ul class="list">
<li class="task">Buy milk</li>
<li class="task done">Read DOM page</li>
<li class="task">Write code</li>
</ul>
<input type="text" name="newTask" />
</main>getElementById
The oldest and fastest selector. Returns a single element or null.
const app = document.getElementById("app");
console.log(app); // <main id="app">…</main>
console.log(app?.tagName); // "MAIN"
const missing = document.getElementById("nope");
console.log(missing); // nullIds are supposed to be unique. If two elements share an id (it happens), getElementById returns the first one in document order.
getElementsByClassName and getElementsByTagName
Return an HTMLCollection — a live, array-like list of matches. The collection updates automatically when the DOM changes.
const tasks = document.getElementsByClassName("task");
console.log(tasks.length); // 3
console.log(tasks[0].textContent); // "Buy milk"
// Live behaviour: add a new .task and the collection grows.
const li = document.createElement("li");
li.className = "task";
li.textContent = "Sleep";
document.querySelector(".list").append(li);
console.log(tasks.length); // 4 — the same variable, now longergetElementsByTagName("li") is the same idea but matches tag names instead of classes.
getElementsByName
Mostly useful for form fields, which often carry a name attribute. Returns a live NodeList.
const inputs = document.getElementsByName("newTask");
console.log(inputs[0].value);querySelector and querySelectorAll
The modern, expressive way to select. They accept any CSS selector — ids, classes, tags, combinators, attribute selectors, pseudo-classes.
document.querySelector("#app"); // first match (or null)
document.querySelector(".task.done"); // a <li> that has both classes
document.querySelector("ul > li:first-child");
document.querySelector("input[name='newTask']");
const all = document.querySelectorAll(".task");
console.log(all.length); // 3
all.forEach((li, i) => console.log(i, li.textContent));0 "Buy milk" 1 "Read DOM page" 2 "Write code"
querySelectorAll returns a NodeList. NodeLists have forEach, length, and indexing, but they are not arrays — no map, filter or reduce directly. Convert with Array.from(list) or [...list] when you need real array methods.
Live vs static collections
This is the subtle bit. Selectors come in two flavours:
Live —
getElementsByClassName,getElementsByTagName,getElementsByName. The returned collection updates as the DOM changes.Static —
querySelectorAll. The returned NodeList is a snapshot taken at the moment of the call. Later DOM changes do not affect it.
live vs static, side by side
const liveTasks = document.getElementsByClassName("task");
const staticTasks = document.querySelectorAll(".task");
console.log(liveTasks.length, staticTasks.length); // 3 3
const li = document.createElement("li");
li.className = "task";
li.textContent = "New one";
document.querySelector(".list").append(li);
console.log(liveTasks.length, staticTasks.length); // 4 3Selecting within an element, not the whole document
Every element has its own querySelector and querySelectorAll. They search only inside that element's subtree, which keeps code modular and faster on big pages.
const list = document.querySelector(".list");
const tasks = list.querySelectorAll(".task"); // only the <li>s inside this <ul>Useful selector tricks
document.querySelectorAll("input, textarea, select")— match several tags at once.document.querySelector("[data-id='42']")— find by data attribute.document.querySelectorAll("li:not(.done)")— exclude with:not.document.querySelectorAll(".task:nth-child(odd)")— every odd item.document.documentElement.querySelector("…")— search from<html>if you ever need to.
When nothing matches
getElementById and querySelector return null. querySelectorAll returns an empty NodeList (length 0), never null. Always check before dereferencing:
const banner = document.querySelector(".promo-banner");
if (banner) {
banner.remove();
}
// Or with optional chaining:
document.querySelector(".promo-banner")?.remove();