Skip to the content

Accessing HTML elements with JavaScript

Last updated: By Lukas BrandtLesson 1 of 114 min read

JavaScript can change almost anything on a page, but it has to get hold of an element first. Every change in this JavaScript course starts the same way: find the element, keep a reference to it in a variable, then do something with that reference. This lesson covers the finding.

The document object

When a browser loads an HTML file, it builds a tree of objects from the markup, one object per element. That tree is the DOM (Document Object Model), and your scripts reach it through a global object called document. Open any page, open the developer console and type these one at a time:

document.title          // the text inside <title>
document.body           // the <body> element itself
document.body.children  // the elements directly inside <body>

The console prints each value. Hover over an element it prints and most browsers highlight that element on the page, a quick way to confirm you grabbed the right thing.

A page to practice on

The examples below all work on this markup. Save it as index.html, with an empty app.js in the same folder.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Bench notes</title>
</head>
<body>
  <h1 id="page-title">Bench notes</h1>
  <p class="note">Clean off the old thermal paste first.</p>
  <p class="note">Check that the fan cable is plugged in.</p>
  <p class="note important">Unplug the power cord before you open the case.</p>
  <button id="toggle-notes" type="button">Hide notes</button>

  <script src="app.js"></script>
</body>
</html>

The <script> tag sits at the end of <body> on purpose. The browser reads the file from top to bottom and runs a plain script the moment it reaches it, so a script placed above the elements would go looking for things that do not exist yet. Lesson 4 shows two tidier ways to handle this. For now, the end of the body does the job.

getElementById

If an element has an id, this is the most direct route to it:

const title = document.getElementById('page-title');
console.log(title.textContent); // "Bench notes"

Pass the id without a #. The hash belongs to CSS selector syntax, and getElementById('#page-title') looks for an id that literally starts with one, finds nothing and returns null. An id should be unique on a page. If two elements share one anyway, you get the first.

querySelector and querySelectorAll

querySelector accepts any CSS selector, exactly as you would write it in a stylesheet, and returns the first matching element, or null when nothing matches.

const firstNote = document.querySelector('.note');
const warning = document.querySelector('.note.important');
const button = document.querySelector('#toggle-notes');
const lastNote = document.querySelector('p.note:last-of-type');

If your selectors are rusty, the CSS selectors lesson covers classes, ids, descendants and combinations, and every one of them works here.

querySelectorAll returns every match, collected in a NodeList:

const notes = document.querySelectorAll('.note');
console.log(notes.length);          // 3
console.log(notes[0].textContent);  // "Clean off the old thermal paste first."
console.log(notes[2].className);    // "note important"

A NodeList looks like an array, with a length and numbered positions starting at 0, but it is not one. It does have a forEach method, which the loops lesson puts to work. When nothing matches, you get an empty NodeList with a length of 0, not null. Remember that difference, because the check you write later depends on it.

Both methods also work on an element instead of on document. someSection.querySelectorAll('p') searches only inside that section, which keeps selectors short and avoids grabbing a paragraph from somewhere else on the page.

Older methods you will run into

getElementsByClassName('note') and getElementsByTagName('p') appear all over older code and still work fine. They return an HTMLCollection, which is live: add another .note to the page later and the collection grows by itself. The NodeList from querySelectorAll is a snapshot of the moment you called it. Neither behavior is wrong, but a snapshot is easier to reason about, which is why newer code tends to use querySelectorAll.

Keep references in const

Store what you find in a variable declared with const:

const notes = document.querySelectorAll('.note');
let visibleCount = notes.length;

const means the variable cannot be pointed at something else later. It does not freeze the element: you can still change its text, classes and styles. Use let for values you plan to reassign, such as the counter above. Tutorials written before 2015 use var, which has looser scoping rules that cause surprises; there is no reason to write it in new code.

Reading what you found

Once you hold an element, a few properties tell you about it:

  • textContent gives all the text inside, with the tags stripped out.
  • innerHTML gives the markup inside as a string.
  • id and className give those attributes. className returns all classes as one string, such as "note important".
  • tagName gives the element's name in capitals, such as P or BUTTON.

Prefer textContent when you only care about text. innerHTML parses whatever you assign to it as HTML, which is fine for markup you wrote yourself and risky for text a visitor typed into a form.

Parts links can earn us a commission. As an Amazon Associate I earn from qualifying purchases.

Try it

Paste the bench-notes page into the online HTML editor, but put the JavaScript inside a <script> element just before </body> instead of loading app.js. Log document.querySelectorAll('.note').length, then add a fourth note to the HTML and run the page again. If the count stays at 3, check that the new paragraph really has class="note" and that the script still comes after it.