Manipulating the DOM
What does it mean to manipulate the DOM?
Manipulating the DOM means changing what’s on the page by updating elements: their text, attributes, classes, styles, or position in the document.
Smallest working example
<button id="toggle">Toggle</button>
<p id="message">Hello</p>
const button = document.querySelector("#toggle");
const message = document.querySelector("#message");
if (button && message) {
button.addEventListener("click", () => {
message.classList.toggle("hidden");
});
}
Why this matters
Most browser interactivity is DOM manipulation:
- Update the UI when something changes (data loaded, user clicks a button)
- Show/hide content and validation messages
- Build and update lists, cards, or tables dynamically
Reading and updating text
Once you've selected an element, you can read and modify its text content.
textContent
textContent gets or sets the plain text content of an element:
const paragraph = document.querySelector("p");
if (paragraph) {
// Read text
console.log(paragraph.textContent); // "Hello World"
// Update text
paragraph.textContent = "New text";
// textContent ignores HTML tags
paragraph.textContent = "<strong>Bold</strong>"; // Displays as text, not HTML
// Result: "<strong>Bold</strong>" (shown as text, not rendered as HTML)
}
Use textContent when: You want plain text only (safer, prevents XSS attacks).
innerHTML
innerHTML gets or sets the HTML content of an element:
const div = document.querySelector("div");
if (div) {
// Read HTML
console.log(div.innerHTML); // "<p>Hello</p>"
// Update HTML
div.innerHTML = "<p>New paragraph</p>";
// innerHTML renders HTML
div.innerHTML = "<strong>Bold</strong>"; // Renders as bold text
}
Use innerHTML when: You need to insert HTML (but be careful with user input—see safety notes below).
Safety with innerHTML
Never put raw user input into innerHTML. It can lead to XSS (Cross-Site Scripting).
const element = document.querySelector("#output");
const userInput = `<img src="x" onerror="alert('XSS')" />`;
if (element) {
// DANGEROUS: user input directly in innerHTML
// element.innerHTML = userInput;
// Safer default: show it as text
element.textContent = userInput;
}
Best practice: Use textContent unless you specifically need HTML. If you must use innerHTML with user input, sanitize it first.
Setting innerHTML replaces an element’s existing children, which can also remove event listeners attached to those child elements.
Attributes
HTML elements have attributes (id, class, src, href, etc.). You can read and modify them:
Reading attributes
const link = document.querySelector("a");
if (link) {
link.id; // Get ID
link.className; // Get class attribute
link.href; // Get href (full URL)
link.getAttribute("href"); // Get href (as written in HTML)
// For custom attributes (data-*)
link.dataset.userId; // Get data-user-id="123"
link.getAttribute("data-user-id"); // Alternative
}
Setting attributes
const img = document.querySelector("img");
if (img) {
// Direct property (for standard attributes)
img.src = "new-image.jpg";
img.alt = "New alt text";
img.id = "myImage";
// Using setAttribute (works for any attribute)
img.setAttribute("src", "new-image.jpg");
img.setAttribute("data-id", "123");
img.setAttribute("aria-label", "Description");
}
Removing attributes
const element = document.querySelector("div");
if (element) {
element.removeAttribute("id");
element.removeAttribute("data-custom");
}
Data attributes
HTML5 data attributes (data-*) are commonly used to store custom data:
<div data-user-id="123" data-status="active">User</div>
const div = document.querySelector("div");
if (div) {
// Access via dataset (converts kebab-case to camelCase)
div.dataset.userId; // "123"
div.dataset.status; // "active"
// Or use getAttribute
div.getAttribute("data-user-id"); // "123"
}
Classes
Managing CSS classes is a common task. The classList property makes this easy:
classList.add()
Add one or more classes:
const element = document.querySelector("div");
if (element) {
element.classList.add("active");
element.classList.add("highlighted", "visible"); // Multiple classes
}
classList.remove()
Remove one or more classes:
const element = document.querySelector("div");
if (element) {
element.classList.remove("active");
element.classList.remove("highlighted", "visible");
}
classList.toggle()
Add the class if it's not present, remove it if it is:
const element = document.querySelector("div");
if (element) {
element.classList.toggle("active"); // Toggle on/off
element.classList.toggle("active", true); // Force add
element.classList.toggle("active", false); // Force remove
}
classList.contains()
Check if an element has a class:
const element = document.querySelector("div");
if (element) {
console.log(element.classList.contains("active"));
}
classList.replace()
Replace one class with another:
const element = document.querySelector("div");
if (element) {
element.classList.replace("old-class", "new-class");
}
Example: Toggle button state
const button = document.querySelector("button");
if (button) {
button.addEventListener("click", () => {
button.classList.toggle("active");
if (button.classList.contains("active")) {
button.textContent = "Active";
return;
}
button.textContent = "Inactive";
});
}
Prefer classes for most styling. Use inline styles mainly for dynamic values (like setting a calculated position).
Inline styles
You can modify inline styles directly via the style property:
const element = document.querySelector("div");
if (element) {
// Set individual styles
element.style.color = "red";
element.style.backgroundColor = "blue";
element.style.fontSize = "18px";
element.style.display = "none";
// Note: CSS properties use camelCase in JavaScript
// CSS: background-color → JavaScript: backgroundColor
// CSS: font-size → JavaScript: fontSize
}
Setting multiple styles
const element = document.querySelector("div");
if (element) {
// Method 1: Set individually
element.style.color = "red";
element.style.fontSize = "18px";
// Method 2: Use cssText (replaces all styles)
element.style.cssText = "color: red; font-size: 18px;";
// Method 3: Object.assign
Object.assign(element.style, {
color: "red",
fontSize: "18px"
});
}
Creating elements
Create new elements dynamically:
// Create element
const newDiv = document.createElement("div");
const newParagraph = document.createElement("p");
const newButton = document.createElement("button");
// Set properties before adding to DOM
newDiv.textContent = "Hello";
newDiv.className = "container";
newParagraph.textContent = "Paragraph text";
Creating with innerHTML
You can also create elements using innerHTML:
const container = document.querySelector("#container");
if (container) {
container.innerHTML = `<div class="item">New item</div>`;
}
innerHTML replaces all existing content. For appending, prefer insertAdjacentHTML().
const container = document.querySelector("#container");
if (container) {
container.insertAdjacentHTML("beforeend", "<div>New item</div>");
}
Appending and removing elements
Appending elements
Add elements to the DOM:
const parent = document.querySelector("#container");
const newDiv = document.createElement("div");
newDiv.textContent = "New element";
if (parent) {
// Add to end
parent.appendChild(newDiv);
// Alternative:
// parent.insertAdjacentElement("beforeend", newDiv);
}
insertAdjacentHTML positions
const element = document.querySelector("#container");
if (element) {
// beforebegin - before the element
element.insertAdjacentHTML("beforebegin", "<div>Before</div>");
// afterbegin - inside, at the start
element.insertAdjacentHTML("afterbegin", "<div>First child</div>");
// beforeend - inside, at the end (like appendChild)
element.insertAdjacentHTML("beforeend", "<div>Last child</div>");
// afterend - after the element
element.insertAdjacentHTML("afterend", "<div>After</div>");
}
Removing elements
const element = document.querySelector("#toRemove");
if (element) {
// Modern alternative (simpler)
element.remove();
}
Example: Dynamic list
function addListItem(text) {
const list = document.querySelector("#myList");
if (!list) {
return;
}
const newItem = document.createElement("li");
newItem.textContent = text;
const deleteButton = document.createElement("button");
deleteButton.textContent = "Delete";
deleteButton.addEventListener("click", () => {
newItem.remove();
});
newItem.appendChild(deleteButton);
list.appendChild(newItem);
}
addListItem("Item 1");
addListItem("Item 2");
Document fragments
When adding many elements, use document fragments for better performance:
// Without fragment (many DOM updates)
const list = document.querySelector("#list");
if (list) {
for (let i = 0; i < 100; i++) {
const item = document.createElement("li");
item.textContent = `Item ${i}`;
list.appendChild(item);
}
}
// With fragment (single append)
const list = document.querySelector("#list");
if (list) {
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const item = document.createElement("li");
item.textContent = `Item ${i}`;
fragment.appendChild(item);
}
list.appendChild(fragment);
}
Fragments let you build elements in memory before adding them to the DOM.
Common patterns
Show/hide elements
const element = document.querySelector("#modal");
if (element) {
// Hide
element.classList.add("hidden");
// Show
element.classList.remove("hidden");
}
Updating multiple elements
document.querySelectorAll(".item").forEach(item => {
item.textContent = "Updated";
item.classList.add("highlighted");
});
Cloning elements
const original = document.querySelector(".template");
if (original) {
const clone = original.cloneNode(true); // true = deep clone
clone.id = "new-id"; // Change ID to avoid duplicates
document.body.appendChild(clone);
}
Summary
- Use
textContentas the safest default for updating text. - Use
innerHTMLonly when you need HTML, and never with raw user input. classListis the cleanest way to toggle styles and UI state.- Use fragments when adding lots of elements.