DOM Manipulation
What is the DOM?
The DOM (Document Object Model) is the browser's live representation of your HTML page as a tree of JavaScript objects. Every element on your page — every heading, paragraph, button, and link — is a node in this tree.
When you change the DOM with JavaScript, the browser immediately updates what the user sees. This is how JavaScript makes pages interactive.
Selecting elements
Before you can change an element, you need to select it. The two most important methods:
querySelector — selects the first element that matches a CSS selector:JavaScript
const title = document.querySelector("h1");
const btn = document.querySelector(".submit-btn");
const form = document.querySelector("#signup-form");querySelectorAll — selects all matching elements (returns a NodeList):JavaScript
const allParagraphs = document.querySelectorAll("p");
allParagraphs.forEach((p) => {
p.style.color = "#333";
});Changing content and attributes
JavaScript
const heading = document.querySelector("h1");
heading.textContent = "New Title";
heading.innerHTML = "New <em>Title</em>"; // Can include HTML
heading.setAttribute("id", "main-heading");
heading.classList.add("highlighted");
heading.classList.remove("hidden");
heading.classList.toggle("active");textContent is safer than innerHTML because it doesn't parse HTML (preventing XSS vulnerabilities).Listening for events
Events are things that happen on the page — clicks, key presses, form submissions, scrolling. You respond to them with event listeners:
JavaScript
const button = document.querySelector("#my-button");
button.addEventListener("click", () => {
console.log("Button was clicked!");
});Common events:
| Event | When it fires |
|---|---|
click | User clicks an element |
input | User types in a text field |
submit | A form is submitted |
keydown | A key is pressed |
mouseover | Mouse enters an element |
A practical example: counter
Here's a complete example that ties it all together:
HTML
<p>Count: <span id="count">0</span></p>
<button id="increment">+1</button>
<button id="reset">Reset</button>JavaScript
let count = 0;
const display = document.querySelector("#count");
document.querySelector("#increment").addEventListener("click", () => {
count += 1;
display.textContent = count;
});
document.querySelector("#reset").addEventListener("click", () => {
count = 0;
display.textContent = count;
});This pattern — select element, listen for event, update DOM — is the foundation of almost every interactive feature on the web.
Creating and removing elements
JavaScript
const list = document.querySelector("#todo-list");
const newItem = document.createElement("li");
newItem.textContent = "Buy groceries";
list.appendChild(newItem);
newItem.remove();Waiting for the DOM with DOMContentLoaded
Scripts in
<head> run before HTML is parsed unless you use defer or listen for the DOM to be ready:JavaScript
document.addEventListener("DOMContentLoaded", () => {
const title = document.querySelector("h1");
title.textContent = "DOM is ready";
});With
<script defer src="app.js"> at the end of <body>, your code usually runs after the tree exists — but DOMContentLoaded is still useful when modules load in unpredictable order.Event delegation
Instead of attaching a listener to every list item, attach one listener to the parent:
JavaScript
document.querySelector("#menu").addEventListener("click", (event) => {
const button = event.target.closest("button[data-action]");
if (!button) return;
if (button.dataset.action === "delete") {
button.closest("li")?.remove();
}
});Delegation works because events bubble up the tree. It's faster for dynamic lists where items are added and removed frequently.
DocumentFragment for batch updates
Appending many nodes one-by-one can trigger multiple reflows. Build offline first:
JavaScript
const fragment = document.createDocumentFragment();
for (const label of ["Read", "Write", "Review"]) {
const li = document.createElement("li");
li.textContent = label;
fragment.appendChild(li);
}
document.querySelector("#tasks").appendChild(fragment);One append to the live DOM minimizes layout work — a small optimization that matters on large lists.
Accessibility and user experience
Interactive pages should work for keyboard users and screen readers, not only mouse clicks. Use semantic HTML (
<button> for actions, <a> for navigation) instead of <div onclick>. Update aria-invalid, aria-expanded, and labels when validation or toggles change state. Focus management matters after opening modals or deleting list items — move focus to the next logical element so users are not stranded.Batch DOM reads and writes when possible. Reading a layout property (like
offsetHeight) forces the browser to recalculate layout; alternating reads and writes in a loop causes layout thrashing. Gather measurements first, then apply changes, or use a DocumentFragment as shown above.Separating structure from behavior
Keep HTML for structure, CSS for presentation, and JavaScript for behavior. Avoid inline
style attributes in favor of CSS classes toggled with classList. Store application state in JavaScript variables (or a framework later) and treat the DOM as a view that reflects that state — the same pattern you will use in the Task Manager project at the end of this track.Key takeaway
The DOM is how JavaScript talks to the page. Master
querySelector, addEventListener, and textContent, and you can build any interactive feature. Start small — build a counter, a toggle, a form validator — and the patterns will click.