DOM Manipulation
Table of Contents
The DOM (Document Object Model) is how JavaScript interacts with web pages. When a browser loads HTML, it creates a tree of objects representing every element on the page. JavaScript can read, modify, add, and remove these objects — which is how you make web pages interactive.
This tutorial covers the essential DOM operations you’ll use constantly in frontend development.
Selecting Elements
Before you can change an element, you need to find it.
querySelector and querySelectorAll
These use CSS selector syntax — if you know CSS, you already know how to use them:
// Select the first match
const heading = document.querySelector("h1");
const submitBtn = document.querySelector("#submit-btn");
const firstCard = document.querySelector(".card");
const emailInput = document.querySelector('input[type="email"]');
// Select ALL matches (returns a NodeList)
const allCards = document.querySelectorAll(".card");
const listItems = document.querySelectorAll("ul.nav > li");
querySelectorAll returns a NodeList, which you can loop over with for...of or forEach:
const buttons = document.querySelectorAll("button");
buttons.forEach(button => {
console.log(button.textContent);
});
getElementById (faster for IDs)
const app = document.getElementById("app");
This is slightly faster than querySelector("#app") but less flexible. Use whichever you prefer — the performance difference is negligible in practice.
Reading and Changing Content
textContent vs innerHTML
const heading = document.querySelector("h1");
// textContent — plain text (safe, no HTML parsing)
console.log(heading.textContent);
heading.textContent = "New Title";
// innerHTML — parses HTML (use carefully)
const container = document.querySelector(".content");
container.innerHTML = "<p>This is <strong>bold</strong> text.</p>";
innerHTML with user-provided data — it creates XSS (cross-site scripting) vulnerabilities. Use textContent for user input, or create elements programmatically.
Changing Styles and Classes
Classes (preferred)
const card = document.querySelector(".card");
// Add/remove/toggle classes
card.classList.add("active");
card.classList.remove("hidden");
card.classList.toggle("expanded"); // adds if missing, removes if present
// Check if a class exists
if (card.classList.contains("active")) {
console.log("Card is active");
}
// Replace a class
card.classList.replace("old-class", "new-class");
Inline styles (use sparingly)
const box = document.querySelector(".box");
box.style.backgroundColor = "blue";
box.style.padding = "20px";
box.style.display = "none";
// Read computed styles (includes CSS file styles)
const computed = getComputedStyle(box);
console.log(computed.fontSize); // "16px"
Prefer classes over inline styles — they’re easier to maintain and keep styling in CSS where it belongs.
Attributes
const link = document.querySelector("a");
// Read attributes
console.log(link.getAttribute("href"));
console.log(link.id); // shorthand for common attributes
// Set attributes
link.setAttribute("href", "https://example.com");
link.setAttribute("target", "_blank");
// Remove attributes
link.removeAttribute("target");
// Check if attribute exists
if (link.hasAttribute("data-id")) {
console.log(link.getAttribute("data-id"));
}
// Data attributes (data-*)
const card = document.querySelector('[data-user-id="123"]');
console.log(card.dataset.userId); // "123" (camelCase conversion)
card.dataset.status = "active"; // sets data-status="active"
Event Handling
Events are how you respond to user interactions — clicks, typing, scrolling, etc.
addEventListener
const button = document.querySelector("#save-btn");
button.addEventListener("click", function (event) {
console.log("Button clicked!");
console.log(event.target); // the element that was clicked
});
// Arrow function version
button.addEventListener("click", (e) => {
e.preventDefault(); // stop default behavior (e.g., form submission)
saveData();
});
Common events
// Click
element.addEventListener("click", handleClick);
// Keyboard
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") submitForm();
});
// Form input
input.addEventListener("input", (e) => {
console.log(e.target.value); // fires on every keystroke
});
// Form submission
form.addEventListener("submit", (e) => {
e.preventDefault(); // prevent page reload
const data = new FormData(form);
console.log(data.get("email"));
});
// Mouse
element.addEventListener("mouseenter", showTooltip);
element.addEventListener("mouseleave", hideTooltip);
// Page load
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM is ready");
});
Event delegation
Instead of adding listeners to many elements, add one to their parent:
// Bad — listener on every button
document.querySelectorAll(".delete-btn").forEach(btn => {
btn.addEventListener("click", handleDelete);
});
// Good — one listener on the parent, works for dynamically added elements too
document.querySelector(".todo-list").addEventListener("click", (e) => {
if (e.target.matches(".delete-btn")) {
const todoItem = e.target.closest(".todo-item");
todoItem.remove();
}
});
Event delegation is especially important when elements are added dynamically — listeners on the parent catch events from elements that didn’t exist when the page loaded.
Removing event listeners
function handleClick() {
console.log("Clicked!");
}
button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick); // must pass same function reference
Creating and Inserting Elements
// Create a new element
const card = document.createElement("div");
card.classList.add("card");
card.textContent = "New card content";
// Insert it into the page
const container = document.querySelector(".container");
container.appendChild(card); // add as last child
container.prepend(card); // add as first child
container.insertBefore(card, reference); // insert before a specific element
// Insert relative to an element
const existing = document.querySelector(".existing");
existing.before(card); // insert before existing
existing.after(card); // insert after existing
existing.replaceWith(card); // replace existing with card
Building complex elements
function createTodoItem(text) {
const li = document.createElement("li");
li.classList.add("todo-item");
const span = document.createElement("span");
span.textContent = text;
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "Delete";
deleteBtn.classList.add("delete-btn");
li.appendChild(span);
li.appendChild(deleteBtn);
return li;
}
const list = document.querySelector(".todo-list");
list.appendChild(createTodoItem("Buy groceries"));
list.appendChild(createTodoItem("Walk the dog"));
Removing Elements
const element = document.querySelector(".old-item");
// Modern way
element.remove();
// Remove a child from its parent
const parent = document.querySelector(".list");
const child = parent.querySelector(".item");
parent.removeChild(child);
Traversing the DOM
Navigate between related elements:
const item = document.querySelector(".current");
// Parent
item.parentElement;
// Children
item.children; // HTMLCollection of child elements
item.firstElementChild;
item.lastElementChild;
// Siblings
item.nextElementSibling;
item.previousElementSibling;
// Closest ancestor matching a selector (searches up the tree)
item.closest(".container");
A Complete Example: Todo List
Putting it all together:
<div id="app">
<form id="todo-form">
<input type="text" id="todo-input" placeholder="Add a task..." required>
<button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
</div>
const form = document.querySelector("#todo-form");
const input = document.querySelector("#todo-input");
const list = document.querySelector("#todo-list");
form.addEventListener("submit", (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
const li = document.createElement("li");
li.innerHTML = `
<span>${text}</span>
<button class="delete-btn" aria-label="Delete task">×</button>
`;
list.appendChild(li);
input.value = "";
input.focus();
});
// Event delegation for delete buttons
list.addEventListener("click", (e) => {
if (e.target.matches(".delete-btn")) {
e.target.parentElement.remove();
}
});
This example demonstrates selecting elements, handling events, creating elements, and event delegation — the core DOM skills you’ll use in every interactive web page.
What’s Next
The DOM is the bridge between JavaScript and the visual page. From here, you can explore Promises and async JavaScript to learn how to fetch data from APIs and update the DOM with dynamic content.