The Document Object Model (DOM) is the live tree-representation of your HTML page. JavaScript manipulates it to make pages interactive — adding elements, changing styles, reacting to clicks. This cheat sheet covers every key DOM method with copy-paste examples.
Quick-reference table
| Task | Method / Property |
|---|---|
| Select one element | document.querySelector(css) |
| Select all matching | document.querySelectorAll(css) |
| Select by id | document.getElementById(id) |
| Select by class | document.getElementsByClassName(cls) |
| Create element | document.createElement(tag) |
| Set text content | el.textContent = 'text' |
| Set HTML content | el.innerHTML = '<b>bold</b>' |
| Get/set attribute | el.getAttribute(name) / el.setAttribute(name, val) |
| Remove attribute | el.removeAttribute(name) |
| Data attributes | el.dataset.myKey |
| Add class | el.classList.add('name') |
| Remove class | el.classList.remove('name') |
| Toggle class | el.classList.toggle('name') |
| Check class | el.classList.contains('name') |
| Set style | el.style.color = 'red' |
| Append child | parent.appendChild(child) |
| Insert before | parent.insertBefore(newEl, refEl) |
| Insert HTML | el.insertAdjacentHTML(pos, html) |
| Clone element | el.cloneNode(deep) |
| Remove element | el.remove() |
| Add event listener | el.addEventListener(event, handler) |
| Remove event listener | el.removeEventListener(event, handler) |
| Parent element | el.parentElement |
| Children | el.children / el.childNodes |
| Next sibling | el.nextElementSibling |
| Previous sibling | el.previousElementSibling |
| First/last child | el.firstElementChild / el.lastElementChild |
Selecting elements
querySelector and querySelectorAll accept any valid CSS selector.
// Single element (first match, or null)
const btn = document.querySelector('#submit-btn');
const first = document.querySelector('.card');
// All matches — returns a static NodeList
const cards = document.querySelectorAll('.card');
cards.forEach(card => card.classList.add('visible'));
// Legacy selectors (still fast, ID-based is fastest)
const el = document.getElementById('main');
const items = document.getElementsByClassName('item'); // live HTMLCollection
const paras = document.getElementsByTagName('p'); // live HTMLCollection
NodeList vs HTMLCollection: querySelectorAll returns a static NodeList (won't update if DOM changes). getElementsBy* returns a live HTMLCollection that auto-updates. Convert either to an array when you need .map() / .filter():
const arr = Array.from(document.querySelectorAll('.item'));
// or
const arr2 = [...document.querySelectorAll('.item')];
Creating and inserting elements
// Create
const div = document.createElement('div');
div.className = 'card';
div.textContent = 'Hello world';
// Append to end of parent
document.body.appendChild(div);
// Insert before a reference element
const ref = document.querySelector('#footer');
document.body.insertBefore(div, ref);
// Modern: append / prepend (accepts multiple nodes and strings)
const ul = document.querySelector('ul');
ul.append(li1, li2, 'text node'); // end
ul.prepend(li0); // start
// insertAdjacentHTML — four positions
// 'beforebegin' — before the element itself
// 'afterbegin' — inside, before first child
// 'beforeend' — inside, after last child
// 'afterend' — after the element itself
el.insertAdjacentHTML('beforeend', '<li>New item</li>');
// insertAdjacentElement / insertAdjacentText for nodes/text
el.insertAdjacentElement('afterend', newEl);
// Clone an element (true = deep clone including children)
const copy = card.cloneNode(true);
document.body.appendChild(copy);
Modifying content
// textContent — plain text, safe from XSS
el.textContent = 'Hello <world>'; // renders literal angle brackets
// innerHTML — parses HTML (XSS risk with user input!)
el.innerHTML = '<strong>Bold</strong>';
// innerText — respects CSS visibility, slower than textContent
el.innerText = 'Visible text only';
// Replace entire children safely (no XSS)
el.replaceChildren(document.createTextNode('Safe text'));
Security: Never use
el.innerHTML = userInput. Always sanitise user-provided HTML with a library like DOMPurify, or usetextContentinstead.
Attributes and data
// Standard attributes
el.getAttribute('href'); // get
el.setAttribute('href', '/new'); // set
el.removeAttribute('disabled'); // remove
el.hasAttribute('hidden'); // boolean check
// Boolean attributes: presence = true
el.setAttribute('disabled', ''); // disable a button
el.removeAttribute('disabled'); // re-enable
// data-* attributes via dataset (camelCase ↔ kebab-case)
// <div data-user-id="42" data-role="admin">
el.dataset.userId; // "42"
el.dataset.role; // "admin"
el.dataset.newKey = 'v'; // sets data-new-key="v"
delete el.dataset.role; // removes data-role
Classes and styles
// classList — preferred for class manipulation
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('open'); // add if absent, remove if present
el.classList.toggle('open', condition); // force add/remove
el.classList.contains('active'); // boolean
el.classList.replace('old-class', 'new-class');
// Multiple classes at once
el.classList.add('foo', 'bar', 'baz');
// Read all classes
el.className; // string: "card active"
[...el.classList]; // array: ["card", "active"]
// Inline styles (camelCase property names)
el.style.color = 'red';
el.style.backgroundColor = '#333';
el.style.fontSize = '1.5rem';
el.style.cssText = 'color: red; font-size: 1.5rem;'; // overwrite all inline styles
// Remove an inline style
el.style.color = '';
// CSS custom properties (variables)
el.style.setProperty('--primary', '#3b82f6');
el.style.getPropertyValue('--primary');
// Read computed style (including stylesheets, not just inline)
const computed = getComputedStyle(el);
computed.color; // e.g. "rgb(0, 0, 0)"
computed.fontSize; // e.g. "16px"
Removing elements
// Modern (IE not supported)
el.remove();
// Legacy (still needed for IE11)
el.parentElement.removeChild(el);
// Remove all children
el.replaceChildren(); // modern
// or
while (el.firstChild) el.removeChild(el.firstChild); // legacy
Traversal
// Up
el.parentElement;
el.closest('.card'); // walk up until CSS selector matches (or null)
// Down
el.children; // live HTMLCollection, elements only
el.childNodes; // live NodeList, includes text and comment nodes
el.firstElementChild;
el.lastElementChild;
el.querySelector('.item'); // deep search
// Sideways
el.nextElementSibling;
el.previousElementSibling;
// Check relationship
parent.contains(child); // true/false
Events
// Add a listener
btn.addEventListener('click', handleClick);
// Named function so it can be removed later
function handleClick(event) {
event.preventDefault(); // stop default (form submit, link follow)
event.stopPropagation(); // stop bubbling up to parent
console.log(event.target); // element that was clicked
console.log(event.currentTarget); // element the listener is on
}
btn.removeEventListener('click', handleClick); // same reference required
// Common event types
// Mouse: click, dblclick, mousedown, mouseup, mouseover, mouseout, mousemove
// Keyboard: keydown, keyup, keypress (deprecated)
// Form: submit, change, input, focus, blur, reset
// Window: load, DOMContentLoaded, resize, scroll, beforeunload
// Once option — auto-removes after first fire
el.addEventListener('click', handler, { once: true });
// Capture phase (fires before bubble)
el.addEventListener('click', handler, { capture: true });
// Event delegation — one listener handles many children
document.querySelector('ul').addEventListener('click', (e) => {
const li = e.target.closest('li'); // find the li even if a child was clicked
if (!li) return;
li.classList.toggle('done');
});
Custom events
// Create and dispatch
const evt = new CustomEvent('cart:updated', {
detail: { count: 3 },
bubbles: true,
});
document.dispatchEvent(evt);
// Listen
document.addEventListener('cart:updated', (e) => {
console.log(e.detail.count); // 3
});
Practical patterns
Toggle show/hide
function toggle(el) {
el.hidden = !el.hidden; // uses the hidden attribute
// or with a class:
el.classList.toggle('is-visible');
}
Build a list from data
const fruits = ['Apple', 'Banana', 'Cherry'];
const ul = document.querySelector('#fruit-list');
// Fragment batches DOM updates into a single paint
const frag = document.createDocumentFragment();
fruits.forEach(name => {
const li = document.createElement('li');
li.textContent = name;
frag.appendChild(li);
});
ul.appendChild(frag);
Wait for DOM ready
// Inline script in <body> (no listener needed)
// Or:
document.addEventListener('DOMContentLoaded', () => {
// DOM is parsed, images may still be loading
init();
});
window.addEventListener('load', () => {
// Everything (images, scripts) is loaded
});
Intersection Observer (lazy load / reveal on scroll)
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target); // stop watching once visible
}
});
}, { threshold: 0.1 }); // 10% visible triggers callback
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
6 common mistakes
| Mistake | Problem | Fix |
|---|---|---|
querySelector returns null |
Element not in DOM yet | Move script to bottom of <body> or use DOMContentLoaded |
el.innerHTML = userInput |
XSS vulnerability | Use el.textContent or sanitise with DOMPurify |
| Removing listener with anonymous function | Never removes | Store reference: const fn = () => {}; el.removeEventListener('click', fn) |
| Modifying DOM in a loop | Many repaints | Use DocumentFragment or build HTML string once |
getElementsByClassName treated as array |
No forEach directly |
Convert: Array.from(...) or use querySelectorAll |
event.target vs event.currentTarget |
Wrong element in delegation | target = clicked element, currentTarget = element with listener |
6 FAQ
Q: querySelector or getElementById — which is faster?getElementById is marginally faster since it uses the browser's ID index directly. For most apps the difference is imperceptible; use querySelector for its flexibility.
Q: Why does my script run before the DOM is ready?
Your <script> tag is in <head> without defer. Add defer to the script tag (<script src="app.js" defer>) or move the script to the bottom of <body>.
Q: What's the difference between children and childNodes?children contains only element nodes. childNodes also includes text nodes (whitespace between tags) and comment nodes. Almost always use children.
Q: Can I use querySelectorAll with forEach directly?
Yes. NodeList has a native .forEach() method. However, it lacks .map(), .filter(), and .reduce() — convert to an array with Array.from() or spread ([...nl]) for those.
Q: How do I read the value of a CSS variable set in a stylesheet?getComputedStyle(el).getPropertyValue('--my-var').trim(). The .trim() removes potential leading whitespace from the stylesheet declaration.
Q: Is it safe to use innerHTML to render HTML I control?
Yes, as long as you never include user-supplied data. For any string that passes through user input, form fields, URL params, or API responses, use textContent or a sanitiser. Even trusted-looking strings can carry injected scripts if the data source is ever compromised.