Chapter 11 Intermediate 40 min min read Updated 2026-04-09

DOM Selection and Manipulation

Practice Questions →

In This Chapter

What Is It?

What Is the DOM?

The DOM (Document Object Model) is a tree-like representation of your HTML page that JavaScript can read and change. When a browser loads an HTML file, it builds a DOM tree where every HTML tag becomes a node (an object). JavaScript can then select these nodes, read their content, and modify them -- changing text, styles, attributes, and structure in real time.

Think of it like this: HTML is the blueprint of a house. The DOM is the actual built house. JavaScript is the person who can walk through the house and repaint walls, move furniture, or add new rooms.

<!-- This HTML... -->
<div id="container">
  <h1 class="title">Hello</h1>
  <p>Welcome to my page</p>
</div>

<!-- ...becomes this tree in the DOM:
  document
    html
      head
      body
        div#container
          h1.title  -> "Hello"
          p         -> "Welcome to my page"
-->

Every element in the DOM is an object with properties and methods. You can use JavaScript to find any element and change it.

Why Does It Matter?

Why Learn DOM Selection and Manipulation?

1. It Is What Makes Websites Interactive

Without DOM manipulation, a webpage is just static text and images. Every button click, every form submission, every dropdown menu, every dark mode toggle -- all of these work because JavaScript selects elements and changes them.

2. It Connects HTML, CSS, and JavaScript

This is where the three languages come together. HTML gives structure, CSS gives style, and JavaScript uses the DOM to read and change both. Understanding the DOM is the bridge between knowing JavaScript and actually building real websites.

3. Every Framework Uses It

React, Vue, Angular -- all of these frameworks are built on top of DOM manipulation. They just hide it behind abstractions. If you understand the raw DOM, you will understand these frameworks much faster when you learn them later.

4. It Is Tested in Interviews and Exams

Questions like "What is the difference between textContent and innerHTML?" or "How do you add a class to an element?" are common in web development interviews. Knowing the DOM well gives you an edge.

Detailed Explanation

Detailed Explanation

1. Selecting Elements by ID

document.getElementById() returns the single element with the matching id. IDs must be unique on a page, so this always returns one element (or null if not found).

<h1 id="main-title">Welcome</h1>

<script>
const title = document.getElementById("main-title");
console.log(title);           // <h1 id="main-title">Welcome</h1>
console.log(title.textContent); // "Welcome"
</script>

2. Selecting with querySelector and querySelectorAll

document.querySelector() takes any CSS selector and returns the first matching element. document.querySelectorAll() returns all matching elements as a NodeList.

<ul>
  <li class="item">Apple</li>
  <li class="item">Banana</li>
  <li class="item active">Cherry</li>
</ul>

<script>
// First .item element
const first = document.querySelector(".item");
console.log(first.textContent); // "Apple"

// The element with both classes
const active = document.querySelector(".item.active");
console.log(active.textContent); // "Cherry"

// All .item elements
const allItems = document.querySelectorAll(".item");
console.log(allItems.length); // 3
allItems.forEach(item => console.log(item.textContent));
// Apple, Banana, Cherry
</script>

querySelectorAll returns a NodeList, not an array. You can use forEach on it, but not map or filter directly. To use array methods, convert it: Array.from(nodeList) or [...nodeList].

3. Changing Text Content

There are three ways to change the text inside an element:

  • textContent -- gets or sets the raw text. Ignores HTML tags.
  • innerText -- similar to textContent but respects CSS visibility (slower).
  • innerHTML -- gets or sets the HTML content. Parses HTML tags.
<p id="demo">Hello <strong>World</strong></p>

<script>
const p = document.getElementById("demo");

console.log(p.textContent); // "Hello World" (raw text, no tags)
console.log(p.innerHTML);   // "Hello <strong>World</strong>" (includes tags)

// Setting text
p.textContent = "New text";   // Replaces everything with plain text
// p is now: <p id="demo">New text</p>

// Setting HTML
p.innerHTML = "New <em>styled</em> text";
// p is now: <p id="demo">New <em>styled</em> text</p>
</script>

Security warning: Never use innerHTML with user input. If a user types <script>alert('hacked')</script>, innerHTML will execute it. This is called XSS (Cross-Site Scripting). Use textContent for user input.

4. Changing Attributes

HTML attributes (like src, href, alt, disabled) can be read and changed with JavaScript:

<img id="photo" src="old.jpg" alt="Old photo">
<a id="link" href="https://example.com">Click me</a>

<script>
const img = document.getElementById("photo");

// Read attributes
console.log(img.getAttribute("src"));  // "old.jpg"
console.log(img.getAttribute("alt"));  // "Old photo"

// Set attributes
img.setAttribute("src", "new.jpg");
img.setAttribute("alt", "New photo");

// Remove attributes
img.removeAttribute("alt");

// Check if attribute exists
console.log(img.hasAttribute("alt")); // false

// Direct property access (for standard attributes)
const link = document.getElementById("link");
console.log(link.href); // "https://example.com"
link.href = "https://google.com";
</script>

5. Changing Styles

You can change CSS styles directly with element.style.property. CSS property names use camelCase in JavaScript:

<p id="text">Styled text</p>

<script>
const text = document.getElementById("text");

// CSS: background-color  ->  JS: backgroundColor
text.style.color = "white";
text.style.backgroundColor = "#a855f7";
text.style.padding = "10px 20px";
text.style.borderRadius = "8px";
text.style.fontSize = "18px";
</script>

This adds inline styles. For toggling styles, it is much better to use CSS classes instead of setting styles directly.

6. Manipulating CSS Classes

The classList property gives you methods to add, remove, toggle, and check CSS classes:

<style>
  .highlight { background-color: yellow; font-weight: bold; }
  .large { font-size: 24px; }
  .hidden { display: none; }
</style>

<p id="text">Some text</p>

<script>
const text = document.getElementById("text");

// Add a class
text.classList.add("highlight");

// Add multiple classes
text.classList.add("highlight", "large");

// Remove a class
text.classList.remove("large");

// Toggle: adds if missing, removes if present
text.classList.toggle("hidden"); // Adds "hidden"
text.classList.toggle("hidden"); // Removes "hidden"

// Check if a class exists
console.log(text.classList.contains("highlight")); // true
console.log(text.classList.contains("large"));     // false
</script>

classList.toggle() is incredibly useful. It returns true if the class was added and false if it was removed.

7. Reading Input Values

To read what a user has typed into an input field, use the .value property:

<input type="text" id="name-input" value="Aarav">
<select id="city-select">
  <option value="delhi">Delhi</option>
  <option value="mumbai" selected>Mumbai</option>
</select>

<script>
const nameInput = document.getElementById("name-input");
console.log(nameInput.value); // "Aarav"

nameInput.value = "Priya"; // Change the input value

const citySelect = document.getElementById("city-select");
console.log(citySelect.value); // "mumbai"
</script>

8. Selecting Multiple Elements and Looping

When you need to change many elements at once, select them all and loop:

<ul id="fruits">
  <li>Apple</li>
  <li>Banana</li>
  <li>Cherry</li>
</ul>

<script>
const items = document.querySelectorAll("#fruits li");

// Add a class to every item
items.forEach(item => {
  item.classList.add("fruit-item");
});

// Change text of all items
items.forEach((item, index) => {
  item.textContent = `Fruit ${index + 1}: ${item.textContent}`;
});
// Now: "Fruit 1: Apple", "Fruit 2: Banana", "Fruit 3: Cherry"
</script>

Code Examples

Selecting and Changing Text
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; }
    #greeting { font-size: 24px; color: #333; }
  </style>
</head>
<body>
  <h1 id="greeting">Hello, Guest!</h1>
  <input type="text" id="name-input" placeholder="Enter your name">
  <button id="update-btn">Update Greeting</button>

  <script>
    const greeting = document.getElementById("greeting");
    const nameInput = document.getElementById("name-input");
    const btn = document.getElementById("update-btn");

    btn.addEventListener("click", function() {
      const name = nameInput.value;
      if (name.trim() !== "") {
        greeting.textContent = "Hello, " + name + "!";
      }
    });
  </script>
</body>
</html>
When the button is clicked, JavaScript reads the value from the input field and updates the h1 text using textContent. The trim() call ensures we do not accept an empty or whitespace-only name.
A page with a heading that says 'Hello, Guest!' -- when you type a name and click the button, the heading changes to greet that name.
Toggling CSS Classes for Dark Mode
<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
      transition: background-color 0.3s, color 0.3s;
    }
    body.dark {
      background-color: #1a1a2e;
      color: #e0e0e0;
    }
    .toggle-btn {
      padding: 10px 20px;
      cursor: pointer;
      border: 2px solid #a855f7;
      background: transparent;
      color: inherit;
      border-radius: 8px;
      font-size: 16px;
    }
  </style>
</head>
<body>
  <h1>My Website</h1>
  <p>This page supports dark mode.</p>
  <button class="toggle-btn" id="theme-btn">Toggle Dark Mode</button>

  <script>
    const btn = document.getElementById("theme-btn");

    btn.addEventListener("click", function() {
      document.body.classList.toggle("dark");

      if (document.body.classList.contains("dark")) {
        btn.textContent = "Toggle Light Mode";
      } else {
        btn.textContent = "Toggle Dark Mode";
      }
    });
  </script>
</body>
</html>
classList.toggle("dark") adds the class if it is missing and removes it if it is present. The CSS transition makes the color change smooth. We also update the button text to reflect the current state using classList.contains().
Clicking the button switches between light and dark backgrounds. The button text updates to reflect the current mode.
Changing Attributes Dynamically
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; text-align: center; }
    img { width: 300px; border-radius: 12px; margin: 10px; }
    button { padding: 8px 16px; margin: 5px; cursor: pointer; }
  </style>
</head>
<body>
  <h2>Image Switcher</h2>
  <img id="display" src="https://placehold.co/300x200/a855f7/white?text=Image+1" alt="Image 1">
  <br>
  <button id="btn1">Image 1</button>
  <button id="btn2">Image 2</button>
  <button id="btn3">Image 3</button>

  <script>
    const img = document.getElementById("display");

    document.getElementById("btn1").addEventListener("click", function() {
      img.setAttribute("src", "https://placehold.co/300x200/a855f7/white?text=Image+1");
      img.setAttribute("alt", "Image 1");
    });

    document.getElementById("btn2").addEventListener("click", function() {
      img.setAttribute("src", "https://placehold.co/300x200/06b6d4/white?text=Image+2");
      img.setAttribute("alt", "Image 2");
    });

    document.getElementById("btn3").addEventListener("click", function() {
      img.setAttribute("src", "https://placehold.co/300x200/f59e0b/white?text=Image+3");
      img.setAttribute("alt", "Image 3");
    });
  </script>
</body>
</html>
Each button uses setAttribute() to change the image's src and alt attributes. This is how image galleries, sliders, and carousels work under the hood.
Three buttons switch which image is displayed by changing the src attribute of the img element.
querySelector with Different CSS Selectors
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; }
    .highlight { background-color: #fef3c7; padding: 4px 8px; }
    .result { margin-top: 10px; padding: 10px; background: #f0f0f0; border-radius: 6px; }
  </style>
</head>
<body>
  <div id="container">
    <h2 class="title">Student List</h2>
    <ul id="student-list">
      <li class="student" data-grade="A">Aarav</li>
      <li class="student" data-grade="B">Priya</li>
      <li class="student active" data-grade="A">Rohan</li>
      <li class="student" data-grade="C">Meera</li>
    </ul>
  </div>
  <div class="result" id="output"></div>

  <script>
    // Select by ID
    const container = document.querySelector("#container");

    // Select by class
    const title = document.querySelector(".title");

    // Select by tag
    const firstLi = document.querySelector("li");

    // Select by attribute
    const gradeA = document.querySelectorAll('[data-grade="A"]');

    // Select by compound selector
    const activeStudent = document.querySelector(".student.active");

    // Select nested element
    const firstInList = document.querySelector("#student-list li:first-child");
    const lastInList = document.querySelector("#student-list li:last-child");

    const output = document.getElementById("output");
    output.innerHTML = 
      "First li: " + firstLi.textContent + "<br>" +
      "Active student: " + activeStudent.textContent + "<br>" +
      "Grade A students: " + gradeA.length + "<br>" +
      "First in list: " + firstInList.textContent + "<br>" +
      "Last in list: " + lastInList.textContent;
  </script>
</body>
</html>
querySelector accepts any valid CSS selector: IDs (#id), classes (.class), tags (li), attributes ([data-grade="A"]), compound selectors (.student.active), and pseudo-selectors (li:first-child). querySelectorAll returns all matches.
Displays: First li: Aarav, Active student: Rohan, Grade A students: 2, First in list: Aarav, Last in list: Meera
Inline Styles vs classList
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; }
    .card {
      padding: 20px;
      border: 1px solid #ddd;
      border-radius: 8px;
      margin: 10px 0;
    }
    .card-purple {
      background-color: #a855f7;
      color: white;
      border-color: #a855f7;
    }
    .card-teal {
      background-color: #06b6d4;
      color: white;
      border-color: #06b6d4;
    }
    button { padding: 8px 16px; margin: 4px; cursor: pointer; }
  </style>
</head>
<body>
  <div class="card" id="my-card">
    <h3>Aarav's Project Card</h3>
    <p>This card changes style when you click the buttons.</p>
  </div>
  <button id="purple-btn">Purple Theme</button>
  <button id="teal-btn">Teal Theme</button>
  <button id="reset-btn">Reset</button>
  <button id="inline-btn">Inline Style (Red Border)</button>

  <script>
    const card = document.getElementById("my-card");

    document.getElementById("purple-btn").addEventListener("click", function() {
      card.classList.remove("card-teal");
      card.classList.add("card-purple");
    });

    document.getElementById("teal-btn").addEventListener("click", function() {
      card.classList.remove("card-purple");
      card.classList.add("card-teal");
    });

    document.getElementById("reset-btn").addEventListener("click", function() {
      card.classList.remove("card-purple", "card-teal");
      card.style.border = "";  // Clear inline style too
    });

    document.getElementById("inline-btn").addEventListener("click", function() {
      card.style.border = "3px solid red";
    });
  </script>
</body>
</html>
This example shows two approaches: classList.add/remove for switching predefined CSS themes, and element.style for setting inline styles. The classList approach is cleaner because styles are defined in CSS. Inline styles override class styles, which is why the red border appears even with a theme class.
Buttons switch the card between purple theme, teal theme, plain, or add a red border via inline style.
Reading and Displaying Input Values
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; }
    .form-group { margin-bottom: 10px; }
    label { display: block; margin-bottom: 4px; font-weight: bold; }
    input, select { padding: 8px; width: 250px; border: 1px solid #ccc; border-radius: 4px; }
    #preview { margin-top: 16px; padding: 16px; background: #f3f4f6; border-radius: 8px; }
    button { padding: 10px 20px; background: #a855f7; color: white; border: none; border-radius: 6px; cursor: pointer; margin-top: 10px; }
  </style>
</head>
<body>
  <h2>Student Profile Preview</h2>
  <div class="form-group">
    <label>Name:</label>
    <input type="text" id="name" placeholder="Enter name">
  </div>
  <div class="form-group">
    <label>Age:</label>
    <input type="number" id="age" placeholder="Enter age">
  </div>
  <div class="form-group">
    <label>City:</label>
    <select id="city">
      <option value="">Select city</option>
      <option value="Delhi">Delhi</option>
      <option value="Mumbai">Mumbai</option>
      <option value="Bangalore">Bangalore</option>
      <option value="Chennai">Chennai</option>
    </select>
  </div>
  <button id="show-btn">Show Preview</button>
  <div id="preview"></div>

  <script>
    document.getElementById("show-btn").addEventListener("click", function() {
      const name = document.getElementById("name").value;
      const age = document.getElementById("age").value;
      const city = document.getElementById("city").value;

      const preview = document.getElementById("preview");

      if (name && age && city) {
        preview.innerHTML = 
          "<strong>Name:</strong> " + name + "<br>" +
          "<strong>Age:</strong> " + age + "<br>" +
          "<strong>City:</strong> " + city;
      } else {
        preview.textContent = "Please fill in all fields.";
      }
    });
  </script>
</body>
</html>
We read values from text inputs and select elements using .value. The preview div is updated using innerHTML (for bold formatting) or textContent (for plain error messages). The && check ensures all fields are filled before displaying.
A form with name, age, and city fields. Clicking 'Show Preview' displays the entered data below the form.
Looping Over querySelectorAll Results
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; }
    .task { padding: 8px 12px; margin: 4px 0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
    .task.done { background-color: #d1fae5; text-decoration: line-through; color: #065f46; }
    button { padding: 8px 16px; margin-top: 10px; cursor: pointer; }
  </style>
</head>
<body>
  <h2>Task List</h2>
  <div class="task">Complete maths homework</div>
  <div class="task">Practice JavaScript DOM</div>
  <div class="task">Read science chapter</div>
  <div class="task">Revise English essay</div>
  <button id="mark-all">Mark All Done</button>
  <button id="clear-all">Clear All</button>

  <script>
    const tasks = document.querySelectorAll(".task");

    // Click individual tasks to toggle done
    tasks.forEach(function(task) {
      task.addEventListener("click", function() {
        task.classList.toggle("done");
      });
    });

    // Mark all done
    document.getElementById("mark-all").addEventListener("click", function() {
      tasks.forEach(function(task) {
        task.classList.add("done");
      });
    });

    // Clear all
    document.getElementById("clear-all").addEventListener("click", function() {
      tasks.forEach(function(task) {
        task.classList.remove("done");
      });
    });
  </script>
</body>
</html>
We use querySelectorAll(".task") to get all task elements, then forEach to loop through them. Each task gets a click handler that toggles the "done" class. The "Mark All" and "Clear All" buttons loop through all tasks and add or remove the class.
A list of tasks. Clicking a task toggles its done state. Buttons mark all as done or clear all.

Common Mistakes

Using getElementById with a # Symbol

const el = document.getElementById("#main-title");
console.log(el); // null!
Returns null because no element has the literal id '#main-title'.
const el = document.getElementById("main-title");
console.log(el); // <h1 id="main-title">...</h1>
getElementById expects just the ID value, without the # symbol. The # is only used in CSS selectors and querySelector. This is a very common mistake.

Trying to Use forEach on getElementsByClassName

const items = document.getElementsByClassName("item");
items.forEach(item => console.log(item)); // TypeError!
TypeError: items.forEach is not a function. getElementsByClassName returns an HTMLCollection, not a NodeList.
// Option 1: Use querySelectorAll instead
const items = document.querySelectorAll(".item");
items.forEach(item => console.log(item));

// Option 2: Convert HTMLCollection to array
const items2 = document.getElementsByClassName("item");
Array.from(items2).forEach(item => console.log(item));
getElementsByClassName returns an HTMLCollection, which does not have forEach. Use querySelectorAll instead (returns a NodeList with forEach), or convert with Array.from().

Using CSS Property Names Instead of camelCase in JS

const el = document.getElementById("box");
el.style.background-color = "red";   // SyntaxError!
el.style.font-size = "20px";          // SyntaxError!
SyntaxError: JavaScript interprets the hyphen as a minus sign.
const el = document.getElementById("box");
el.style.backgroundColor = "red";
el.style.fontSize = "20px";
CSS uses hyphens (background-color) but JavaScript uses camelCase (backgroundColor). Remove the hyphen and capitalize the next letter: font-size becomes fontSize, border-radius becomes borderRadius.

Forgetting that querySelector Returns Only the First Match

// HTML: <li class="item">A</li> <li class="item">B</li> <li class="item">C</li>
const items = document.querySelector(".item");
console.log(items.length); // undefined! items is a single element
querySelector returns one element, not a collection. There is no .length property on a single element.
// Use querySelectorAll for multiple elements
const items = document.querySelectorAll(".item");
console.log(items.length); // 3
querySelector returns the first matching element. querySelectorAll returns all matching elements as a NodeList. If you need multiple elements, always use querySelectorAll.

Script Runs Before HTML Elements Exist

<!-- This script is in the <head> -->
<head>
  <script>
    const title = document.getElementById("title");
    title.textContent = "New Title"; // TypeError: Cannot set properties of null
  </script>
</head>
<body>
  <h1 id="title">Old Title</h1>
</body>
TypeError: Cannot set properties of null (reading 'textContent'). The element does not exist yet when the script runs.
<!-- Option 1: Place script at the end of body -->
<body>
  <h1 id="title">Old Title</h1>
  <script>
    const title = document.getElementById("title");
    title.textContent = "New Title"; // Works!
  </script>
</body>

<!-- Option 2: Use DOMContentLoaded -->
<head>
  <script>
    document.addEventListener("DOMContentLoaded", function() {
      const title = document.getElementById("title");
      title.textContent = "New Title";
    });
  </script>
</head>
The browser reads HTML top to bottom. If your script is in the head, the body elements do not exist yet. Either place your script at the end of the body, or wrap it in a DOMContentLoaded event listener.

Summary

  • The DOM (Document Object Model) is a tree of objects representing your HTML page. JavaScript can read, change, add, and remove any part of it.
  • document.getElementById('id') selects one element by its ID. Do not include the # symbol -- that is only for CSS selectors.
  • document.querySelector('.class') selects the first element matching a CSS selector. document.querySelectorAll('.class') selects all matches and returns a NodeList.
  • textContent gets or sets plain text. innerHTML gets or sets HTML content (can parse tags). Never use innerHTML with user input due to XSS security risks.
  • setAttribute(), getAttribute(), removeAttribute(), and hasAttribute() let you read and change HTML attributes like src, href, alt, and data-* attributes.
  • element.style.propertyName sets inline CSS styles. Use camelCase in JavaScript: background-color becomes backgroundColor, font-size becomes fontSize.
  • element.classList provides add(), remove(), toggle(), and contains() methods for managing CSS classes. This is the preferred way to change styles.
  • The .value property reads or sets the current value of input, textarea, and select elements. This is how you get what the user has typed.
  • querySelectorAll returns a NodeList, not an array. You can use forEach on it, but for map/filter, convert it with Array.from() or the spread operator.
  • Always place your script at the end of the body or use DOMContentLoaded to ensure HTML elements exist before your JavaScript tries to select them.

Ready to Practice?

Test your understanding with 50+ practice questions on this topic.

Go to Practice Questions

Want to learn web development with a live mentor?

Explore our Frontend Masterclass