Quick Answer

DOM manipulation is how JavaScript reads and changes what you see on a web page. You grab an element with a method like document.querySelector, then update its text, HTML, or style, or add and remove elements. To make things interactive, you attach an event listener such as a click handler with addEventListener. Learn these few steps and you can build almost any small interactive feature.

What Is JavaScript DOM Manipulation?

If you have ever wanted a button to actually do something, or a list to grow when you click "Add", you need JavaScript DOM manipulation. It is the skill that turns a static HTML page into something a user can interact with, and it is usually the first genuinely fun thing beginners build.

DOM stands for Document Object Model. When the browser loads your HTML, it does not just show the text — it builds a live tree of objects in memory, one object per tag. That tree is the DOM. Your <h1>, each <p>, and every <button> becomes a node you can reach from JavaScript.

"Manipulation" simply means reading and changing that tree after the page has loaded: swapping text, changing colours, adding new elements, or removing old ones. The page updates instantly, with no reload. Everything in this guide runs right in the browser — open any page, press F12, and use the Console tab to try the snippets live.

Selecting Elements: getElementById and querySelector

Before you can change an element, you have to select it — hand JavaScript a reference to that node. There are two methods you will use constantly.

getElementById

The oldest and fastest. You pass the value of an element's id attribute (without the #):

<p id="greeting">Hello</p>

<script>
  const greeting = document.getElementById("greeting");
  console.log(greeting); // the <p> element
</script>

querySelector and querySelectorAll

querySelector is the flexible modern option: you give it any CSS selector, and it returns the first element that matches.

document.querySelector("#greeting");   // by id, note the #
document.querySelector(".btn");        // first element with class "btn"
document.querySelector("ul li");       // first <li> inside a <ul>

Need every match, not just the first? Use querySelectorAll, which returns a list you can loop over:

const buttons = document.querySelectorAll(".btn");
buttons.forEach((btn) => console.log(btn.textContent));

Gotcha: with getElementById you pass just "greeting", but with querySelector you must include the # for an id or the . for a class, exactly like in CSS. Mixing these up is the most common early mistake.

Changing Text, HTML, and Styles

Text: textContent

Once you hold an element, textContent reads or sets its text:

const title = document.querySelector("#title");
title.textContent = "Welcome to Priodemy";

HTML: innerHTML

innerHTML lets you set actual HTML markup inside an element, not just plain text:

const box = document.querySelector("#box");
box.innerHTML = "<strong>Saved!</strong>";

Important safety note: only use innerHTML with content you control. Never drop text a user typed straight into innerHTML — a malicious user could inject a <script> and attack your visitors. For anything a user typed, prefer textContent, which treats the value as plain text.

Styles: the style property and classList

You can set inline styles directly. CSS property names become camelCase (background-color becomes backgroundColor):

title.style.color = "teal";
title.style.backgroundColor = "#f4f4f4";

But the cleaner approach is to define classes in your CSS and toggle them with classList:

title.classList.add("active");
title.classList.remove("hidden");
title.classList.toggle("active"); // add if missing, remove if present

Keeping the styling in CSS and only flipping classes from JavaScript keeps your code tidy and easy to change later.

Creating and Removing Elements

Real apps do not just edit existing elements — they build new ones. That takes three steps: create, fill, and attach.

// 1. create a new element (it lives only in memory so far)
const li = document.createElement("li");

// 2. fill it with content
li.textContent = "Learn the DOM";

// 3. attach it to the page
const list = document.querySelector("#list");
list.appendChild(li);

Until you call appendChild (or the newer append), the element is invisible — it is not part of the page yet. appendChild adds it as the last child of the parent.

Removing is even simpler. Modern browsers give every element a remove method:

const oldItem = document.querySelector("#list li");
oldItem.remove(); // gone from the page

That is the whole toolkit: createElement to make a node, textContent to fill it, appendChild or append to place it, and remove to delete it. With just these you can build lists, cards, and menus on the fly.

Handling Clicks with addEventListener

Selecting and changing elements is useful, but the magic happens when the page reacts to the user. That is what addEventListener is for. You tell an element which event to listen for — "click", "input", "submit" and so on — and give it a function to run when that event fires.

const btn = document.querySelector("#save");

btn.addEventListener("click", () => {
  alert("Saved!");
});

The function you pass is called a handler. It runs every time the event happens. You can also read details of the event through the event object the browser hands in:

btn.addEventListener("click", (event) => {
  console.log("You clicked", event.target);
});

You may have seen the older onclick attribute written inside HTML. Prefer addEventListener: it keeps JavaScript out of your HTML, and it lets you attach several handlers to the same element without one overwriting another.

Project 1: Build a Tiny Counter

Let us put it all together. This counter selects elements, listens for clicks, and updates text — the core loop of almost every interactive feature.

The HTML:

<button id="decrease">-</button>
<span id="count">0</span>
<button id="increase">+</button>

The JavaScript:

let count = 0;
const countEl = document.getElementById("count");

document.getElementById("increase").addEventListener("click", () => {
  count = count + 1;
  countEl.textContent = count;
});

document.getElementById("decrease").addEventListener("click", () => {
  count = count - 1;
  countEl.textContent = count;
});

Notice the pattern: a variable (count) holds the state, the click handlers change that variable, and then we write the new value back to the page with textContent. That "state, then event, then update the DOM" loop is the beating heart of front-end development, and it is exactly what frameworks like React automate for you later.

Project 2: A Mini To-Do Widget

Now a slightly bigger widget that also creates and removes nodes. Type a task, click Add, and it appears in the list with its own Delete button.

The HTML:

<input id="task" placeholder="What needs doing?">
<button id="add">Add</button>
<ul id="list"></ul>

The JavaScript:

const input = document.getElementById("task");
const list = document.getElementById("list");

document.getElementById("add").addEventListener("click", () => {
  const text = input.value.trim();
  if (text === "") return; // ignore empty input

  const li = document.createElement("li");
  li.textContent = text;

  const del = document.createElement("button");
  del.textContent = "Delete";
  del.addEventListener("click", () => li.remove());

  li.appendChild(del);
  list.appendChild(li);

  input.value = ""; // clear the box for the next task
  input.focus();
});

Every idea from this guide is here: we select the input and list, read a value, create an <li> and a button, attach a click handler that removes the item, and finally append everything to the page. Fewer than twenty lines, and it genuinely works.

Common Gotchas and Where to Go Next

Run your script after the HTML exists

If your script runs before the browser has created the elements, your selectors return null and nothing works. The easy fix is to put your <script> tag just before the closing </body> tag, or add the defer attribute: <script src="app.js" defer></script>.

"Cannot read properties of null"

This very common error means your selector matched nothing, so you are calling a method on null. Check the spelling of the id or class, and make sure you included the # or . in querySelector.

Do not rebuild the whole page with innerHTML

Setting innerHTML on a big container wipes and rebuilds everything inside it, which also throws away any event listeners you had attached. Prefer createElement and append for adding items, as we did in the to-do widget.

That is genuinely enough to start building. Practise by extending the two widgets — make the counter refuse to go below zero, or let the to-do list save to localStorage. When you want the full path from the basics to real projects, our free JavaScript course covers the DOM, events, and much more, step by step.

Frequently Asked Questions

What is the difference between textContent and innerHTML?

textContent gets or sets plain text and treats everything as literal characters, so it is safe for anything a user typed. innerHTML parses its value as HTML, which is handy for adding markup but risky with user input because it can run injected code. Use textContent by default and reach for innerHTML only with content you control.

Why does document.querySelector return null?

It returns null when nothing on the page matches your selector. The usual causes are a typo in the id or class, a missing # or . in the selector, or a script that runs before the element exists. Put your script at the end of the body or add defer, and double-check the selector text.

Should I use getElementById or querySelector?

Both are fine. getElementById is a touch faster and reads clearly when you are selecting by id. querySelector is more flexible because it accepts any CSS selector — ids, classes, or nested rules — so many people use it everywhere for consistency. As a beginner, pick whichever you find clearer and stay consistent.

What does addEventListener do that onclick does not?

addEventListener lets you attach multiple handlers to the same element and event without them overwriting each other, and it keeps your JavaScript out of your HTML. The old onclick attribute allows only one handler and mixes code into markup. For anything beyond a quick test, prefer addEventListener.

Do I need a framework like React to manipulate the DOM?

No. Everything in this guide is plain, built-in JavaScript that runs in every browser with no libraries. Frameworks like React exist to manage the DOM for you once apps get large, but understanding the raw methods first makes those tools far easier to learn. Start here, then move up when you feel the need.