DocsHub
DOM

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 CSS

textContent 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 button

hasAttribute()

console.log(btn.hasAttribute("disabled")); // false
console.log(btn.hasAttribute("id"));       // true

Direct 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 CSS

CSS property names with hyphens become camelCase in JavaScript:

CSSJavaScript
background-colorbackgroundColor
font-sizefontSize
border-radiusborderRadius
margin-topmarginTop
z-indexzIndex

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 applied

Working 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 list

prepend() — add as first child

const firstItem = document.createElement("li");
firstItem.textContent = "Introduction";

list.prepend(firstItem);
// "Introduction" is now the first item

insertBefore() — 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 item

insertAdjacentHTML() — 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 completely

removeChild() — 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 fast
  • innerHTML — read or set HTML content — never use with user input
  • getAttribute() / 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 camelCase
  • classList.add(), classList.remove(), classList.toggle() — the right way to manage styles
  • createElement() — creates a new element — must be inserted to appear on the page
  • appendChild(), prepend(), insertAdjacentHTML() — insert elements
  • remove() — remove an element from the DOM
  • cloneNode(true) — deep copy an element with all its children
  • Always prefer toggling classes over setting inline styles directly

On this page