The Document Object Model (DOM) is the browser's representation of your HTML page as a tree of objects. JavaScript can read and modify this tree, which is how you make web pages interactive.
Selecting Elements
getElementById
Select a single element by its id attribute:
const header = document.getElementById("main-header");
console.log(header.textContent);querySelector and querySelectorAll
Select elements using CSS selectors — the most versatile approach:
// Single element (first match)
const button = document.querySelector(".submit-btn");
const nav = document.querySelector("nav");
const input = document.querySelector('input[type="email"]');
// Multiple elements (NodeList)
const items = document.querySelectorAll(".list-item");
items.forEach((item) => {
console.log(item.textContent);
});Comparison of Selection Methods
| Method | Returns | Use When |
|---|---|---|
getElementById | Element | You have a unique ID |
querySelector | Element | You need the first CSS match |
querySelectorAll | NodeList | You need all matching elements |
getElementsByClassName | HTMLCollection | Legacy code (prefer querySelector) |
Reading and Modifying Content
textContent vs innerHTML
<div id="output">
<strong>Hello</strong> World
</div>const el = document.querySelector("#output");
console.log(el.textContent); // "Hello World" (plain text)
console.log(el.innerHTML); // "<strong>Hello</strong> World" (HTML string)
// Set content
el.textContent = "New plain text"; // safe — escapes HTML
el.innerHTML = "<em>New HTML</em>"; // renders HTML — be careful with user input
Always use textContent when inserting user-generated content to prevent cross-site scripting (XSS) attacks.
Modifying Styles and Classes
Working with Classes
const card = document.querySelector(".card");
card.classList.add("active");
card.classList.remove("hidden");
card.classList.toggle("expanded");
console.log(card.classList.contains("active")); // true
Inline Styles
const box = document.querySelector(".box");
box.style.backgroundColor = "#E21B1B";
box.style.padding = "1rem";
box.style.display = "none";Prefer toggling CSS classes over setting inline styles — it keeps your JavaScript clean and your styles in CSS where they belong.
Modifying Attributes
const link = document.querySelector("a");
// Read
console.log(link.getAttribute("href"));
// Set
link.setAttribute("href", "https://sabaoon.dev");
link.setAttribute("target", "_blank");
// Remove
link.removeAttribute("title");
// Data attributes
const card = document.querySelector(".card");
card.dataset.userId = "123"; // sets data-user-id="123"
console.log(card.dataset.userId); // "123"
Creating and Inserting Elements
createElement
const list = document.querySelector("#todo-list");
const newItem = document.createElement("li");
newItem.textContent = "Learn DOM manipulation";
newItem.classList.add("todo-item");
list.appendChild(newItem);Building Multiple Elements
const fruits = ["Apple", "Banana", "Cherry"];
const ul = document.querySelector("#fruit-list");
const fragment = document.createDocumentFragment();
fruits.forEach((fruit) => {
const li = document.createElement("li");
li.textContent = fruit;
fragment.appendChild(li);
});
ul.appendChild(fragment);Using a DocumentFragment is more efficient than appending elements one by one because it causes only a single reflow.
insertAdjacentHTML
A convenient way to insert HTML at specific positions:
const container = document.querySelector(".container");
container.insertAdjacentHTML("beforebegin", "<p>Before the container</p>");
container.insertAdjacentHTML("afterbegin", "<p>First child</p>");
container.insertAdjacentHTML("beforeend", "<p>Last child</p>");
container.insertAdjacentHTML("afterend", "<p>After the container</p>");Removing Elements
const item = document.querySelector(".remove-me");
item.remove();
// Remove a child from its parent
const parent = document.querySelector(".list");
const child = parent.querySelector(".old-item");
parent.removeChild(child);Event Handling
Events are how you respond to user interactions.
addEventListener
const button = document.querySelector("#submit");
button.addEventListener("click", (event) => {
console.log("Button clicked!");
console.log("Target:", event.target);
});Common Events
| Event | Fires When |
|---|---|
click | Element is clicked |
input | Input value changes |
submit | Form is submitted |
keydown | Key is pressed |
mouseover | Cursor enters an element |
focus | Element gains focus |
blur | Element loses focus |
load | Page or resource finishes loading |
Event Delegation
Instead of adding listeners to many child elements, add one listener to a parent:
const list = document.querySelector("#task-list");
list.addEventListener("click", (event) => {
if (event.target.matches(".delete-btn")) {
const taskItem = event.target.closest(".task-item");
taskItem.remove();
}
});Event delegation is more memory-efficient and works for dynamically added elements.
Preventing Default Behavior
const form = document.querySelector("#login-form");
form.addEventListener("submit", (event) => {
event.preventDefault(); // stop the page from reloading
const formData = new FormData(form);
const email = formData.get("email");
const password = formData.get("password");
console.log("Logging in with:", email);
});Practical Exercise
Build an interactive to-do list:
<input id="todo-input" type="text" placeholder="Add a task..." />
<button id="add-btn">Add</button>
<ul id="todo-list"></ul>const input = document.querySelector("#todo-input");
const addBtn = document.querySelector("#add-btn");
const list = document.querySelector("#todo-list");
function addTask() {
const text = input.value.trim();
if (!text) return;
const li = document.createElement("li");
li.innerHTML = `
<span clas="task-text">${text}</span>
<button clas="delete-btn">Delete</button>
`;
li.querySelector(".task-text").addEventListener("click", () => {
li.classList.toggle("completed");
});
list.appendChild(li);
input.value = "";
input.focus();
}
addBtn.addEventListener("click", addTask);
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") addTask();
});
// Event delegation for delete buttons
list.addEventListener("click", (event) => {
if (event.target.matches(".delete-btn")) {
event.target.parentElement.remove();
}
});Key Takeaways
- Use
querySelectorandquerySelectorAllfor selecting elements — they accept any CSS selector. - Prefer
textContentoverinnerHTMLwhen inserting user-generated content to prevent XSS. - Toggle CSS classes instead of setting inline styles for cleaner separation of concerns.
- Use
DocumentFragmentwhen inserting many elements to minimize reflows. - Event delegation on a parent element is more efficient than individual listeners on each child.