JavaScript DOM Manipulation
Select, edit, style, and create DOM elements with vanilla JavaScript and handle user events.
TL;DR
- 01Select elements with
querySelector()andgetElementById()before reading or editing them. - 02Edit content with
textContent, and toggle styles withclassList. - 03Attach
addEventListener()to respond to clicks and other user actions.
Tips
- 01Use
textContentinstead ofinnerHTMLwhen inserting user-provided text, since it prevents HTML injection and runs faster. - 02Attach one
addEventListener()call to a parent element instead of many on children, so new elements work automatically through delegation.
Warnings
- 01Adding many elements one by one causes slow page reflows, so use a
DocumentFragmentfor batch inserts in large lists. - 02Setting
innerHTMLwith untrusted or user-supplied content opens the door to script injection, so sanitize it or usetextContentinstead.
Selecting Elements
getElementById()Faster than querySelector, but only matches elements by their unique ID.
const title = document.getElementById('title');querySelector()Finds the first element that matches any valid CSS selector.
const btn = document.querySelector('.btn');querySelectorAll()Gets every matching element as a static NodeList for looping.
const els = document.querySelectorAll('.item');NodeList methodsNodeLists support forEach, but use Array.from() for full array methods.
Array.from(items).map(el => el.textContent);Cache selectionsCache repeated selections in a variable to keep your code fast.
const btn = document.querySelector('.btn');Editing Content
textContentEscapes everything you assign to it, unlike innerHTML, which parses markup.
title.textContent = 'New Title';innerHTMLInserts HTML markup, but only with trusted content since it executes scripts.
title.innerHTML = '<em>New Title</em>';Reading textRead element text the same way you set it, using the same properties.
console.log(title.textContent);insertAdjacentHTML()Inserts markup at a specific position without replacing existing content.
list.insertAdjacentHTML('beforeend', html);Template literalsUse template literals to build dynamic strings before assigning them.
title.innerHTML = `<em>${name}</em> logged in`;Styling Elements
Inline stylesWork well for one-off changes, but classList methods scale better overall.
btn.style.backgroundColor = 'tomato';classList.add()Adds a CSS class to the element for cleaner styling control.
btn.classList.add('active');classList.toggle()Toggles a class on and off with a single method call.
btn.classList.toggle('highlight');classList.remove()Removes a class when the state changes back to normal.
btn.classList.remove('active');classList.contains()Checks whether an element currently has a given class applied.
btn.classList.contains('active');Creating and Removing
createElement()Builds a new element that isn't yet attached to the DOM.
const newDiv = document.createElement('div');
newDiv.textContent = 'Hello!';appendChild() / append()Inserts the new element into the DOM; append() also accepts strings and multiple arguments.
document.body.appendChild(newDiv);remove()Removes an element from the DOM by calling remove() on it directly.
newDiv.remove();insertBefore() / prepend()Use these to control the exact position where new elements land.
list.prepend(newItem); // inserts firstDocumentFragmentBatches multiple appends into one operation, avoiding repeated page reflows.
const frag = document.createDocumentFragment();
items.forEach(i => frag.append(i));
list.append(frag);Traversing the DOM
parentElementAccesses the direct parent of the current element, or null.
const parent = btn.parentElement;childrenAccesses all direct children of an element as a live HTMLCollection.
const items = list.children; // live collectionSibling propertiesMove to the next or previous sibling at the same level.
const next = el.nextElementSibling;
const prev = el.previousElementSibling;closest()Finds the nearest matching ancestor starting from any element, or null.
const card = btn.closest('.card');contains()Checks whether one element is a descendant of another, anywhere in the tree.
document.body.contains(btn); // true or falseIn Practice
Creates new list items dynamically and uses one delegated click listener on the parent to handle removal for every item.
- 01addTodo() builds a new li with createElement, then append() attaches a remove button inside it.
- 02One click listener on the parent list handles every item — event delegation means new items work without extra listeners.
- 03event.target checks which element was actually clicked, since the listener fires from the parent.
- 04closest('li') walks up from the clicked button to find the row that needs to be removed.
const list = document.querySelector('#todo-list');
function addTodo(text) {
const li = document.createElement('li');
li.textContent = text;
li.classList.add('todo-item');
const removeBtn = document.createElement('button');
removeBtn.textContent = 'Remove';
removeBtn.classList.add('remove-btn');
li.append(removeBtn);
list.append(li);
}
list.addEventListener('click', (event) => {
if (event.target.classList.contains('remove-btn')) {
event.target.closest('li').remove();
}
});
addTodo('Buy groceries');
addTodo('Walk the dog');
// clicking any "Remove" button, even on future items, removes its rowFAQ
getElementById() is slightly faster and only searches by ID. querySelector() accepts any CSS selector, like class, tag, or attribute, making it more flexible. Use getElementById() when you have an ID, querySelector() for everything else.
Use element.classList.add('myClass'), classList.remove('myClass'), or classList.toggle('myClass') for conditional toggling. Avoid directly manipulating element.className as a string, since that overwrites all existing classes.
Use document.createElement('tag') to create the element, set its properties, then append it with parentElement.appendChild(newElement) or parentElement.append(newElement). The append method also accepts plain strings and multiple arguments, making it more versatile.
Event listeners attached with addEventListener only apply to elements that exist at the time of binding, not dynamically added ones. Use event delegation instead: attach the listener to a stable parent element and check event.target inside the handler.
Use getAttribute('src') and setAttribute('src', value) to work with the raw HTML attribute string. Access the property directly, like element.src, to get the resolved, live value instead. The property is usually more convenient, but attributes are necessary for custom or non-reflected attributes like data-* or aria-*.