DOM Manipulation

May 18, 2026
#javascript #js #dom #web-development

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>";

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.