blog / guides

selecting DOM elements in JavaScript: a practical beginner guide

by Luke published 8 min read

the short answer

Use document.querySelector() with a CSS selector to get the first element that matches, and document.querySelectorAll() to get every match. For one element with an id, document.getElementById() also works. Once you have the element, you can read it or change it.

To select an element on a web page with JavaScript, use document.querySelector() and give it a CSS selector. It gives back the first element that matches. To get every match, use document.querySelectorAll():

const heading = document.querySelector("h1");
const pets = document.querySelectorAll(".pet");

Selecting is always the first step. You cannot change a heading, hide a message or react to a button until your code has found it. This guide explains what the DOM is, the three ways of selecting you will use most, how to handle a selector that finds nothing, and the mistakes that trip up nearly every beginner.

These examples need a real web page to work on, so they are shown rather than run here. You can try every one in your browser’s console, and there is a way to try it on this very page further down.

What the DOM is

When a browser loads a page, it reads the HTML and builds a live copy of it in memory. That copy is a tree of objects: the page at the top, then the <html> element, then <head> and <body>, then everything inside them. It is called the DOM, short for Document Object Model.

JavaScript cannot touch your HTML file. It works with the DOM instead. Change the DOM and the page on screen changes straight away.

document is your way in. It stands for the whole page, and every selecting method starts from it.

The page used in this guide

Every example below works on this small page:

<h1 id="title">Pet shop</h1>
<p class="note">Open every day.</p>
<ul id="pets">
  <li class="pet">Cat</li>
  <li class="pet">Dog</li>
  <li class="pet sold">Fish</li>
</ul>
<button>Adopt</button>

querySelector: the first match

document.querySelector() takes a CSS selector, written as text in quotes, and gives back the first element that matches. textContent then reads the words inside it:

const title = document.querySelector("#title");
console.log(title.textContent);
Pet shop

The selector is exactly the kind you write in CSS. The ones you will use most:

  • "h1" finds an element by its tag name.
  • ".note" finds an element by its class. Note the dot.
  • "#pets" finds an element by its id. Note the hash.
  • "#pets .pet" finds a .pet somewhere inside #pets.
  • ".pet.sold" finds an element that has both classes.
  • "li:first-child" finds an li that is the first child of its parent.

Because querySelector() stops at the first match, querySelector(".pet") gives you the Cat item, even though there are three items with that class.

querySelectorAll: every match

document.querySelectorAll() gives back every element that matches, in the order they appear on the page. The result is called a NodeList. It has a length, and you can loop over it:

const pets = document.querySelectorAll(".pet");
console.log(pets.length);
pets.forEach((pet) => console.log(pet.textContent));
3
Cat
Dog
Fish

You can also pick one item by its position, counting from 0. pets[2].textContent is "Fish".

If nothing matches, you get an empty NodeList with a length of 0, not an error. That makes querySelectorAll() safe to loop over even when the page has nothing to find.

getElementById: the older way to find an id

document.getElementById() finds one element by its id. You give it the id on its own, with no hash:

const list = document.getElementById("pets");
console.log(list.children.length);
3

It does the same job as querySelector("#pets"). You will see it a lot in older code and tutorials. Either is fine, but using querySelector() everywhere means you only have one method to remember.

Two more you may meet

getElementsByClassName() and getElementsByTagName() also exist. They give back a live list that updates by itself when the page changes, which can surprise you inside a loop. Beginners are better off with querySelectorAll(), whose list stays exactly as it was when you asked.

Searching inside an element

Every element has its own querySelector() and querySelectorAll(). Call them on an element and the search stays inside it:

const list = document.querySelector("#pets");
const sold = list.querySelector(".sold");
console.log(sold.textContent);
Fish

This is useful on big pages, where the same class can appear in many places. Find the right section first, then search inside it.

When nothing matches: null

If querySelector() cannot find a match, it gives back null, which means “nothing”. The trouble starts on the next line, when you try to use it:

const missing = document.querySelector("#price");
console.log(missing);
console.log(missing.textContent);

The first log shows null. The second stops the script. Chrome and Edge report it as TypeError: Cannot read properties of null (reading 'textContent'). Firefox words it differently, but it means the same thing: you asked a missing element for its text.

When an element might not be there, check before you use it:

const price = document.querySelector("#price");
if (price) {
  console.log(price.textContent);
} else {
  console.log("No price on this page");
}
No price on this page

Your script might run too early

A browser reads a page from top to bottom. If your <script> tag sits in the <head>, it runs before the <body> has been read, so there is nothing to select yet and every querySelector() gives back null.

There are two simple fixes:

  • Put the script at the end of the body, just before </body>.
  • Add defer to the script tag, like <script src="app.js" defer></script>. The browser then waits until the page is read before running it.

Common mistakes

Forgetting the dot or the hash

querySelector("pets") looks for a tag called pets, which does not exist, so it gives back null. Ids need # and classes need . in a selector.

Putting a hash in getElementById

getElementById("#pets") looks for an id that literally starts with a hash. It finds nothing and gives back null. The hash belongs in selectors only.

Treating a NodeList as one element

Asking a whole NodeList for its textContent gives undefined, because the list itself has no text. Loop over it, or pick one item, like [0].

Calling array methods on a NodeList

pets.map(...) stops with TypeError: pets.map is not a function. A NodeList has forEach but not map or filter. Wrap it first: Array.from(pets).map(...).

Getting the capital letters wrong

JavaScript cares about capitals. document.queryselector stops with TypeError: document.queryselector is not a function. It is querySelector, with a capital S.

Using the same id twice

An id should appear once per page. If two elements share one, getElementById() quietly gives you the first and ignores the other. Use a class for anything that repeats.

Try it on this page

You are reading a web page right now, so you can practise on it. Open your browser’s console, which is Ctrl, Shift and J in Chrome or Edge on Windows, or Cmd, Option and J in Chrome on a Mac. Then type:

document.querySelector("h1").textContent

The console shows the title of this article. Now count the section headings on this page:

document.querySelectorAll("h2").length

Nothing you type there can break the page for anyone else, and a refresh puts everything back.

If the console is new to you, our guide to JavaScript console.log() explains how to open and use it.

A short exercise

Using the pet shop page from earlier, log the name of every pet that has not been sold. Your output should be:

Cat
Dog

Hint: CSS has a :not() selector, which matches elements that do not match the selector inside the brackets.

One possible answer

const available = document.querySelectorAll(".pet:not(.sold)");
available.forEach((pet) => console.log(pet.textContent));

Your next step

Selecting elements is the gateway to everything interactive on the web. Gwecode’s JavaScript Level 2 starts right here, with a live page next to your code, then moves on to changing words, reacting to clicks and building whole lists from data. Its level check is a working shopping list app. Every lesson is free and runs in your browser. Create a free account to keep your progress, then start with the lesson below.

practise this · javascript · lessonMeet the pagestart the lesson

questions

What is the DOM?

DOM stands for Document Object Model. It is the browser’s live copy of a web page, built from the HTML as a tree of objects that JavaScript can read and change.

What is the difference between querySelector and getElementById?

getElementById only finds an element by its id. querySelector finds an element using any CSS selector, including an id. Both give back the first match, or null if there is none.

Why does querySelector return null?

Nothing on the page matched the selector when that line ran. Check the spelling, the # or the dot, and that your script runs after the HTML it is looking for.

Is a NodeList an array?

Not quite. You can loop over it with forEach or for...of, but array methods like map are missing. Array.from() turns it into a real array.

Can I select elements inside another element?

Yes. Call querySelector or querySelectorAll on that element instead of on document, and the search stays inside it.

keep going.