Skip to content
elephantoo

The DOM: select, create & modify

Lesson 13 of 34 16 min read

Find elements, change text, classes and attributes, create and remove nodes, and handle a first click.


The DOM (Document Object Model) is the browser's live, in-memory tree of your HTML page. Every tag becomes an element node that JavaScript can read and change — and when you change the tree, the page updates. That's how every interactive website works, and it's what frameworks like React do for you under the hood.

The document is a tree#

HTML
<!doctype html>
<html lang="en">
  <body>
    <header>
      <h1 id="title">Elephantoo</h1>
    </header>
    <ul id="lessons">
      <li class="lesson done">Intro</li>
      <li class="lesson">Variables</li>
    </ul>
    <button id="add">Add lesson</button>
    <script src="app.js" defer></script>
  </body>
</html>

document is the root. <html> contains <body>, which contains <header>, <ul> and so on. Elements have parents, children and siblings, just like a family tree.

Try it: open DevTools, pick the Elements tab, click an element, then type $0 in the Console — it refers to the selected element.

Selecting elements#

JavaScript
const title = document.querySelector("#title");           // by id
const firstLesson = document.querySelector(".lesson");    // first match
const lessons = document.querySelectorAll("#lessons li"); // all matches
const doneOnes = document.querySelectorAll("li.done");
const byId = document.getElementById("title");            // fast, id only

console.log(lessons.length);             // 2
lessons.forEach((li) => console.log(li.textContent));
  • querySelector(css) returns the first match or null if nothing matches.
  • querySelectorAll(css) returns a static NodeList. It has forEach; convert with [...list] or Array.from(list) to use map and filter.
  • You can search inside any element: list.querySelector("li").

Any CSS selector works: "nav a.active", "[data-id='3']", "input[type=email]", "li:last-child".

Moving around the tree

JavaScript
const list = document.querySelector("#lessons");
console.log(list.children.length);         // 2 – element children
console.log(list.firstElementChild.textContent); // Intro
console.log(list.parentElement.tagName);   // BODY
const second = list.children[1];
console.log(second.previousElementSibling.textContent); // Intro
console.log(second.closest("ul").id);      // lessons – nearest matching ancestor

closest is especially useful with events, as you'll see in the next lesson.

Changing content#

JavaScript
const title = document.querySelector("#title");

title.textContent = "Welcome to Elephantoo"; // plain text – safe
console.log(title.textContent);

const note = document.createElement("p");
note.innerHTML = "<strong>Tip:</strong> practise daily"; // parses HTML

Use textContent for text. innerHTML parses its string as HTML, so never put user input into it — a "name" like <img src=x onerror="stealCookies()"> would run code. That attack is called cross-site scripting (XSS).

Classes, styles and attributes#

JavaScript
const title = document.querySelector("#title");

title.classList.add("highlight");
title.classList.remove("hidden");
title.classList.toggle("active");          // add if missing, remove if present
console.log(title.classList.contains("highlight")); // true

title.style.color = "teal";                // inline style (camelCase properties)
title.style.fontSize = "2rem";

title.setAttribute("aria-live", "polite");
console.log(title.getAttribute("id"));     // title
title.removeAttribute("aria-live");

Prefer toggling classes and keeping the styles in CSS — it's easier to maintain than setting many inline styles.

Data attributes

Store small bits of data on elements with data-* attributes:

HTML
<button class="enrol" data-course-id="react" data-price="0">Enrol</button>
JavaScript
const btn = document.querySelector(".enrol");
console.log(btn.dataset.courseId); // "react"
console.log(Number(btn.dataset.price)); // 0 – dataset values are strings
btn.dataset.state = "loading";     // adds data-state="loading"

Form element properties

Many attributes are also properties:

JavaScript
const input = document.querySelector("input");
input.value = "Ada";        // current text in the box
input.disabled = true;
const checkbox = document.querySelector("input[type=checkbox]");
checkbox.checked = true;

Creating, inserting and removing elements#

JavaScript
const list = document.querySelector("#lessons");

const item = document.createElement("li");
item.textContent = "Functions";
item.classList.add("lesson");

list.append(item);          // add as the last child
list.prepend(document.createElement("li")); // add as the first child
list.firstElementChild.remove(); // remove an element

const heading = document.querySelector("#title");
heading.after(document.createElement("hr"));    // insert after
heading.before(document.createTextNode("🐘 ")); // insert before

append accepts several nodes and strings: list.append(item1, item2, "text").

Building many elements efficiently

Every insert can make the browser recalculate layout. When adding lots of items, build them first and insert once:

JavaScript
const titles = ["Loops", "Scope", "Arrays", "Objects"];
const list = document.querySelector("#lessons");

const fragment = document.createDocumentFragment();
for (const t of titles) {
  const li = document.createElement("li");
  li.textContent = t;
  fragment.append(li);
}
list.append(fragment); // one insertion

// Or replace all children at once:
// list.replaceChildren(...titles.map(makeLi));

Templates for bigger chunks

For complex markup, use an HTML <template> and clone it:

HTML
<template id="card-template">
  <article class="card">
    <h3 class="card-title"></h3>
    <p class="card-body"></p>
  </article>
</template>
<div id="cards"></div>
JavaScript
const template = document.querySelector("#card-template");
const cards = document.querySelector("#cards");

function addCard(title, body) {
  const card = template.content.cloneNode(true); // deep copy of the template
  card.querySelector(".card-title").textContent = title;
  card.querySelector(".card-body").textContent = body;
  cards.append(card);
}

addCard("DOM", "Select, create and modify elements.");

This keeps HTML in HTML and is safe, because you fill in values with textContent.

Your first event#

Pages become interactive when you listen for events:

JavaScript
const button = document.querySelector("#add");
const list = document.querySelector("#lessons");
let count = list.children.length;

button.addEventListener("click", () => {
  count += 1;
  const li = document.createElement("li");
  li.textContent = `Lesson ${count}`;
  list.append(li);
});

addEventListener(type, handler) registers a function to run each time the event happens. Common types: click, input, change, submit, keydown, pointerenter, scroll. The next lesson dives into events properly.

A tiny complete example#

Save this as a single HTML file and open it:

HTML
<!doctype html>
<html lang="en">
  <body>
    <label>Your name <input id="name" /></label>
    <p id="greeting">Hi, stranger!</p>
    <script>
      const input = document.querySelector("#name");
      const greeting = document.querySelector("#greeting");
      input.addEventListener("input", () => {
        greeting.textContent = `Hi, ${input.value.trim() || "stranger"}!`;
      });
    </script>
  </body>
</html>

Type in the box and the paragraph updates live — and because it uses textContent, typing <b>hi</b> shows the tags as text rather than running HTML.

Common mistakes#

  • Cannot read properties of null — your selector matched nothing, or the script ran before the element existed. Check the selector and use defer or type="module".
  • Calling querySelectorAll(...).map — NodeLists don't have map; spread them into an array first.
  • Using innerHTML += in a loop — it re-parses everything each time, destroys event listeners and risks XSS.
  • Forgetting that dataset and input.value are always strings.

What's next#

You can find and change anything on the page. Next, master events: the event object, bubbling, delegation and keyboard handling.

Check your understanding

Quick quiz

0/3 answered
  1. 1.Which method returns the first element matching a CSS selector?

  2. 2.Why prefer textContent over innerHTML for user-provided text?

  3. 3.How do you read the value of <div data-lesson-id="42"> in JavaScript?

Finished reading?

Mark this lesson complete to track your progress.