DOM Manipulation
Learn how to read and change content, attributes, styles, and elements in the DOM using JavaScript.
DOM Manipulation
Selecting elements is step one. Step two is actually doing something with them — changing their text, updating styles, adding or removing elements, toggling classes.
This is where JavaScript makes a page come alive. Every change you make to the DOM updates the page instantly — no refresh needed.
Reading and Changing Text Content
textContent — plain text
Gets or sets the plain text inside an element. Any HTML tags inside are treated as text, not markup.
const heading = document.querySelector("h1");
// Reading
console.log(heading.textContent); // "Welcome to DocsHub"
// Writing
heading.textContent = "Hello, Ali!";
// The h1 now shows "Hello, Ali!"If the element has nested tags, textContent returns all the text combined:
// <div id="card"><h2>Title</h2><p>Description</p></div>
const card = document.getElementById("card");
console.log(card.textContent); // "TitleDescription"innerHTML — HTML content
Gets or sets the HTML inside an element. Unlike textContent, it parses and renders HTML tags.
const card = document.querySelector(".card");
// Reading
console.log(card.innerHTML);
// <h2 class="card-title">JavaScript</h2><p>The language of the web.</p>
// Writing — renders as real HTML
card.innerHTML = "<h2>Python</h2><p>Great for data science.</p>";Never use innerHTML with user-provided content — it is a security risk. If user input contains <script> tags or event handlers, injecting it with innerHTML can execute malicious code. This is called XSS (Cross-Site Scripting). Use textContent for plain text and only use innerHTML for trusted, controlled content.
innerText vs textContent
// textContent — returns all text including hidden elements
// innerText — returns only visible text, respects CSStextContent is faster and returns everything. innerText is aware of styling — if an element is hidden with display: none, innerText ignores it. Use textContent in most cases.
Reading and Changing Attributes
getAttribute() and setAttribute()
const btn = document.querySelector("button");
// Read
console.log(btn.getAttribute("id")); // "submit-btn"
console.log(btn.getAttribute("data-action")); // "submit"
console.log(btn.getAttribute("disabled")); // null — does not exist
// Write
btn.setAttribute("disabled", "true");
btn.setAttribute("data-action", "cancel");removeAttribute()
btn.removeAttribute("disabled"); // enables the buttonhasAttribute()
console.log(btn.hasAttribute("disabled")); // false
console.log(btn.hasAttribute("id")); // trueDirect property access
For common attributes — id, href, src, value, placeholder, disabled, checked — you can get and set them directly as properties:
const input = document.querySelector("input");
const link = document.querySelector("a");
const img = document.querySelector("img");
// Reading
console.log(input.value); // current input value
console.log(input.placeholder); // placeholder text
console.log(link.href); // full URL
console.log(img.src); // full image URL
// Writing
input.value = "new value";
link.href = "https://docshub.dev";
img.src = "/images/new-photo.png";
btn.disabled = true;Direct property access is cleaner and faster than getAttribute/setAttribute for standard HTML attributes.
Changing Styles
style property — inline styles
Set CSS properties directly on an element. Use camelCase for multi-word properties.
const heading = document.querySelector("h1");
heading.style.color = "blue";
heading.style.fontSize = "2rem";
heading.style.backgroundColor = "lightyellow";
heading.style.padding = "1rem";
heading.style.display = "none"; // hide element
heading.style.display = ""; // remove inline style — falls back to CSSCSS property names with hyphens become camelCase in JavaScript:
| CSS | JavaScript |
|---|---|
background-color | backgroundColor |
font-size | fontSize |
border-radius | borderRadius |
margin-top | marginTop |
z-index | zIndex |
Reading computed styles
element.style only shows inline styles you set with JavaScript. To read styles applied by a CSS file, use getComputedStyle():
const heading = document.querySelector("h1");
const styles = getComputedStyle(heading);
console.log(styles.color); // "rgb(0, 0, 0)"
console.log(styles.fontSize); // "32px"
console.log(styles.fontFamily); // the actual font appliedWorking With Classes
Directly setting element.style for every change gets messy fast. The better approach is to define styles in CSS and toggle classes with JavaScript.
classList — the right way to manage classes
const card = document.querySelector(".card");
// Add a class
card.classList.add("active");
card.classList.add("highlighted", "bordered"); // add multiple at once
// Remove a class
card.classList.remove("active");
// Toggle — adds if missing, removes if present
card.classList.toggle("dark-mode");
// Check if class exists
console.log(card.classList.contains("active")); // true or false
// Replace one class with another
card.classList.replace("old-class", "new-class");Why classes are better than inline styles
// ❌ Inline style — hard to manage, mixes concerns
element.style.backgroundColor = "blue";
element.style.color = "white";
element.style.padding = "1rem";
element.style.borderRadius = "8px";
// ✅ Class toggle — clean, all styles live in CSS
element.classList.add("active");In your CSS:
.active {
background-color: blue;
color: white;
padding: 1rem;
border-radius: 8px;
}One class add instead of four style assignments. The styles live where they belong — in CSS.
Creating and Adding Elements
createElement() — create a new element
const newParagraph = document.createElement("p");
newParagraph.textContent = "This was added by JavaScript.";
newParagraph.classList.add("note");Creating an element does not add it to the page. You need to insert it somewhere.
appendChild() — add as last child
const list = document.getElementById("list");
const newItem = document.createElement("li");
newItem.textContent = "Closures";
newItem.classList.add("item");
list.appendChild(newItem);
// "Closures" is now the last item in the listprepend() — add as first child
const firstItem = document.createElement("li");
firstItem.textContent = "Introduction";
list.prepend(firstItem);
// "Introduction" is now the first iteminsertBefore() — insert before a specific element
const referenceItem = document.querySelector(".item:nth-child(2)");
const newItem = document.createElement("li");
newItem.textContent = "Data Types";
list.insertBefore(newItem, referenceItem);
// newItem is now before the second list iteminsertAdjacentHTML() — insert HTML at specific positions
A powerful method that inserts HTML at four possible positions relative to an element:
const card = document.querySelector(".card");
card.insertAdjacentHTML("beforebegin", "<p>Before the card</p>");
card.insertAdjacentHTML("afterbegin", "<p>Inside, at the top</p>");
card.insertAdjacentHTML("beforeend", "<p>Inside, at the bottom</p>");
card.insertAdjacentHTML("afterend", "<p>After the card</p>");beforebegin → <div class="card">
afterbegin → existing content
beforeend → </div>
afterend →Removing Elements
remove() — remove the element itself
const item = document.querySelector(".item");
item.remove(); // removes it from the DOM completelyremoveChild() — remove a child from a parent
const list = document.getElementById("list");
const firstItem = list.firstElementChild;
list.removeChild(firstItem);remove() is simpler and more modern. Use it in all new code.
Cloning Elements
cloneNode() — copy an element
const card = document.querySelector(".card");
// Shallow clone — copies the element but not its children
const shallowCopy = card.cloneNode(false);
// Deep clone — copies the element and all its children
const deepCopy = card.cloneNode(true);
document.body.appendChild(deepCopy);Useful when you need multiple copies of the same structure.
A Real Example — Dynamic Todo 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.getElementById("todo-input");
const addBtn = document.getElementById("add-btn");
const todoList = document.getElementById("todo-list");
function addTodo() {
const text = input.value.trim();
if (!text) return; // do nothing if input is empty
// Create list item
const li = document.createElement("li");
li.textContent = text;
// Create delete button
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "Delete";
deleteBtn.classList.add("delete-btn");
// Delete button removes the list item
deleteBtn.addEventListener("click", () => {
li.remove();
});
// Click the text to toggle done
li.addEventListener("click", () => {
li.classList.toggle("done");
});
// Add delete button to list item
li.appendChild(deleteBtn);
// Add list item to the list
todoList.appendChild(li);
// Clear input
input.value = "";
}
addBtn.addEventListener("click", addTodo);
// Also add on Enter key
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") addTodo();
});This example brings together everything in this topic — creating elements, setting text, adding classes, appending to the DOM, removing elements, and responding to events. A fully working todo list in under 40 lines.
Summary
textContent— read or set plain text, safe and fastinnerHTML— read or set HTML content — never use with user inputgetAttribute()/setAttribute()— read and write any attribute- Direct properties —
element.value,element.href,element.disabled— cleaner for standard attributes element.style.property— set inline styles, use camelCaseclassList.add(),classList.remove(),classList.toggle()— the right way to manage stylescreateElement()— creates a new element — must be inserted to appear on the pageappendChild(),prepend(),insertAdjacentHTML()— insert elementsremove()— remove an element from the DOMcloneNode(true)— deep copy an element with all its children- Always prefer toggling classes over setting inline styles directly