← THE CODEX
JAVASCRIPT10 MIN READ

JavaScript DOM Manipulation: How to Make Web Pages Actually Do Things

ElvarBY ELVAR

Learn how JavaScript interacts with the DOM to select elements, change content, modify styles, and respond to user events. A practical beginner's guide to DOM manipulation.

HTML builds the structure. CSS makes it look right. But neither of them responds when a user clicks a button, fills out a form, or types into a search box.

That's JavaScript's job — specifically, JavaScript's ability to read and rewrite the DOM. Understanding DOM manipulation is the moment web development stops being static and starts feeling alive.

What Is the DOM?

When a browser loads an HTML page, it doesn't just display it — it builds a representation of the entire page in memory. This representation is called the Document Object Model, or DOM.

The DOM is a tree. At the top is document — the entire page. Below that, every HTML element becomes a node: the <body> contains a <main>, which contains an <article>, which contains an <h1>, and so on, all the way down to the individual text inside each element.

JavaScript can access this tree, walk through it, read it, and modify it. When JavaScript changes a node, the browser immediately updates what's visible on screen — no page reload required.

Step 1: Selecting Elements

Before you can change anything, you need to find it. JavaScript gives you several ways to select elements from the DOM.

querySelector — Your Primary Tool

// Select by CSS selector — returns the first match, or null
const heading = document.querySelector('h1');
const button  = document.querySelector('.submit-btn');
const panel   = document.querySelector('#user-panel');

querySelector accepts any valid CSS selector — tag names, classes, IDs, attribute selectors, combinators. It returns the first element that matches, or null if nothing matches.

querySelectorAll — When You Need Multiple Elements

// Returns a NodeList of all matching elements
const allCards = document.querySelectorAll('.card');

allCards.forEach(card => {
  console.log(card.textContent);
});

Always Check for null

const panel = document.querySelector('#user-panel');

// Without this check, your code crashes if the element doesn't exist
if (panel) {
  panel.style.display = 'block';
}

If you call a method on null, you get a TypeError that crashes your script. Check first.

Step 2: Reading and Writing Content

Once you have an element, you can read what's inside it and write new content.

textContent — Safe for Plain Text

const title = document.querySelector('h1');

// Read
console.log(title.textContent); // "Welcome to Devstiny"

// Write
title.textContent = 'Quest Complete';

textContent gets or sets the plain text of an element and all its descendants. It's safe because it doesn't parse HTML — if you write <strong> into textContent, it shows up as literal characters, not bold text.

innerHTML — For When You Need HTML

const card = document.querySelector('.card');
card.innerHTML = '<h2>New Title</h2><p>New description.</p>';

innerHTML parses and renders HTML, so you can insert tags. But there's a critical warning: never set innerHTML from user input. If a user types <script>alert("hacked")</script> and you put that directly into innerHTML, it executes. This is called an XSS vulnerability. Use textContent for user-generated content, always.

Step 3: Changing Classes and Styles

The cleanest way to change how an element looks is to add or remove CSS classes. Define the visual state in your CSS file, then toggle it with JavaScript.

// In your CSS
.hidden      { display: none; }
.card--active { border-color: #4ECDC4; }

// In your JavaScript
const card  = document.querySelector('.card');
const modal = document.querySelector('.modal');

card.classList.toggle('card--active');  // Toggle on/off
modal.classList.add('hidden');          // Add a class
modal.classList.remove('hidden');       // Remove a class

if (card.classList.contains('card--active')) {
  console.log('Card is active');
}

Prefer classList — it keeps your CSS in your CSS file. You can also set inline styles directly, but this is generally less clean and should be avoided for anything defined in a stylesheet.

Step 4: Creating and Removing Elements

JavaScript can build entirely new elements and add them to the page, or remove existing ones.

// Create a new element
const newCard = document.createElement('div');
newCard.className = 'card';
newCard.textContent = 'New item';

// Add it to the page
const container = document.querySelector('.container');
container.appendChild(newCard); // Adds at the end
container.prepend(newCard);     // Adds at the beginning

// Remove an element
const oldItem = document.querySelector('.outdated-item');
oldItem.remove();

This is the foundation of dynamic web apps — when you load more results without refreshing the page, when you add items to a cart, when a notification appears in the corner — this is what's happening.

Step 5: Responding to Events

The most important part of DOM manipulation is making things happen in response to what users do.

addEventListener

const button = document.querySelector('#submit-btn');

button.addEventListener('click', function(event) {
  console.log('Button clicked');
  event.preventDefault(); // Prevent default form submission
});

The first argument is the event name. The second is a callback function that runs when the event fires.

Common events:

EventTriggers When
clickUser clicks an element
submitA form is submitted
inputUser types in an input field
changeA select/checkbox/radio changes
keydownA key is pressed
mouseenter / mouseleaveMouse enters or leaves an element

Event Delegation: One Listener for Many Elements

Adding individual listeners to 100 list items is inefficient. Event delegation attaches a single listener to a parent element and uses event.target to handle clicks on its children.

const list = document.querySelector('.items-list');

list.addEventListener('click', function(event) {
  // Check if a list item was clicked
  if (event.target.matches('.item')) {
    event.target.classList.toggle('item--selected');
  }
});

// Works even for elements added to the list AFTER the listener was attached

Putting It Together: A Complete Example

const input    = document.querySelector('#task-input');
const addButton = document.querySelector('#add-btn');
const taskList  = document.querySelector('#task-list');

// Add a task
addButton.addEventListener('click', function() {
  const text = input.value.trim();
  if (!text) return;

  const li = document.createElement('li');
  li.className = 'task-item';
  li.textContent = text;

  const removeBtn = document.createElement('button');
  removeBtn.textContent = '×';
  removeBtn.className = 'remove-btn';
  li.appendChild(removeBtn);

  taskList.appendChild(li);
  input.value = '';
});

// Remove a task (event delegation)
taskList.addEventListener('click', function(event) {
  if (event.target.matches('.remove-btn')) {
    event.target.parentElement.remove();
  }
});

The Key Principles to Remember

What Comes After DOM Manipulation

Once you're comfortable with the DOM, the next territory is making network requests — fetching data from APIs without reloading the page (the Fetch API and async/await). After that, you'll find that frameworks like React are largely abstractions over the same ideas: selecting elements, managing state, and responding to events.

The DOM is where JavaScript starts to feel like real power.

In Devstiny's Chapter 4, the DOM is taught through The Wired District — a city where every element exists but nothing renders. Somers, the realm's jester-logician, guides you through selection, manipulation, and events to restore the district node by node. Start learning at devstiny.com.