What is the DOM?

When a browser loads an HTML page, it creates a tree of objects from your HTML — this is the Document Object Model (DOM). Every HTML element becomes a node in this tree. JavaScript can access, modify, create, or delete any of these nodes.

Think of the DOM as a live, programmable version of your HTML. When JavaScript changes the DOM, the browser immediately updates what the user sees — without reloading the page.

The DOM is not JavaScript — it is a browser API that JavaScript uses. document is the global object that represents the entire page and is your entry point to all DOM operations.

Selecting Elements

Before you can change anything, you need to find the element. These are the main methods:

// Select by CSS selector — returns first match
const title = document.querySelector('h1');
const btn = document.querySelector('#submit-btn');
const card = document.querySelector('.card');

// Select ALL matches — returns NodeList
const items = document.querySelectorAll('.item');
const headings = document.querySelectorAll('h2, h3');

// Older methods (still widely used)
const byId = document.getElementById('myId');
const byClass = document.getElementsByClassName('myClass'); // HTMLCollection
const byTag = document.getElementsByTagName('div');         // HTMLCollection

// Iterating querySelectorAll results
items.forEach(item => {
  console.log(item.textContent);
});

Use querySelector and querySelectorAll — they accept any CSS selector, making them the most flexible and consistent choice.

Reading Element Content

const el = document.querySelector('.card');

// Read text content (no HTML tags)
console.log(el.textContent);     // "Hello World"

// Read HTML content (includes tags) — use with caution
console.log(el.innerHTML);       // "<strong>Hello</strong> World"

// Read attribute values
const link = document.querySelector('a');
console.log(link.getAttribute('href'));    // "/about"
console.log(link.href);                    // full URL
console.log(link.id);
console.log(link.className);

Modifying Content and Attributes

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

// Set text content — SAFE (no XSS risk)
heading.textContent = 'New Heading Text';

// Set HTML content — USE CAREFULLY (XSS risk with user data)
heading.innerHTML = 'New <strong>Heading</strong>';

// Set/remove attributes
const img = document.querySelector('img');
img.setAttribute('alt', 'A beautiful sunset');
img.setAttribute('src', 'new-image.jpg');
img.removeAttribute('title');

// Direct property access (common attributes)
const input = document.querySelector('input');
input.value = 'Default text';
input.disabled = true;
input.placeholder = 'Enter your name';

Security rule: Always use textContent (not innerHTML) when inserting user-provided data. Setting innerHTML with untrusted input can lead to XSS (Cross-Site Scripting) attacks.

Changing Styles and Classes

const box = document.querySelector('.box');

// Inline styles (camelCase property names)
box.style.backgroundColor = '#6d28d9';
box.style.padding = '1rem';
box.style.display = 'none';          // hide
box.style.display = '';              // remove inline style (restore CSS)

// Classes — preferred over inline styles
box.classList.add('active');
box.classList.remove('hidden');
box.classList.toggle('dark-mode');   // adds if absent, removes if present
box.classList.contains('active');    // true or false
box.classList.replace('old-class', 'new-class');

// Check current computed style
const style = window.getComputedStyle(box);
console.log(style.backgroundColor);  // actual rendered color

Prefer toggling CSS classes over setting inline styles. It keeps your styling logic in CSS where it belongs, and makes your JS code cleaner.

Creating and Inserting Elements

// Create a new element
const newItem = document.createElement('li');
newItem.textContent = 'New Todo Item';
newItem.classList.add('todo-item');

// Insert into the DOM
const list = document.querySelector('#todo-list');
list.appendChild(newItem);                    // add at end
list.prepend(newItem);                        // add at start
list.insertBefore(newItem, list.children[2]); // insert at specific position

// Modern: insertAdjacentElement — very flexible
const heading = document.querySelector('h2');
heading.insertAdjacentElement('afterend', newItem);
// Positions: 'beforebegin', 'afterbegin', 'beforeend', 'afterend'

// Create multiple elements efficiently
const names = ['Alice', 'Bob', 'Carol'];
const ul = document.querySelector('ul');

names.forEach(name => {
  const li = document.createElement('li');
  li.textContent = name;
  ul.appendChild(li);
});

Removing Elements

// Modern: remove() — removes itself
const item = document.querySelector('.item');
item.remove();

// Old way: removeChild() — removes a child
const parent = document.querySelector('#list');
const child = parent.querySelector('.item');
parent.removeChild(child);

// Remove all children (clear a list)
const list = document.querySelector('ul');
list.innerHTML = '';    // fast but bypasses DOM events
// OR
while (list.firstChild) {
  list.removeChild(list.firstChild);  // safe, preserves memory
}

Adding Event Listeners

// Basic click handler
const btn = document.querySelector('#my-btn');
btn.addEventListener('click', function(event) {
  console.log('Button clicked!');
  console.log(event.target);    // the element that was clicked
});

// Arrow function syntax
btn.addEventListener('click', (e) => {
  e.preventDefault();           // stop default browser behavior
  e.stopPropagation();          // stop event bubbling
});

// Common events
document.addEventListener('DOMContentLoaded', () => {
  // DOM is ready — safe to query elements
});

const input = document.querySelector('input');
input.addEventListener('input', (e) => {
  console.log('Current value:', e.target.value);
});

window.addEventListener('scroll', () => {
  console.log('Scroll Y:', window.scrollY);
});

// Event delegation — one listener for many elements
const list = document.querySelector('#item-list');
list.addEventListener('click', (e) => {
  if (e.target.classList.contains('delete-btn')) {
    e.target.closest('li').remove();
  }
});

Event delegation is a powerful pattern: attach one listener to a parent element instead of one listener per child. This is more performant and automatically works for dynamically added elements.

A Practical Example: Todo List

Putting it all together — a simple add/remove todo list:

// HTML assumed:
// <input id="todo-input" placeholder="Add a task...">
// <button id="add-btn">Add</button>
// <ul id="todo-list"></ul>

const input = document.querySelector('#todo-input');
const addBtn = document.querySelector('#add-btn');
const list = document.querySelector('#todo-list');

addBtn.addEventListener('click', () => {
  const text = input.value.trim();
  if (!text) return;               // ignore empty input

  // Create list item
  const li = document.createElement('li');

  // Create text node SAFELY (no XSS)
  const span = document.createElement('span');
  span.textContent = text;

  // Create delete button
  const del = document.createElement('button');
  del.textContent = 'Delete';
  del.addEventListener('click', () => li.remove());

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

  input.value = '';        // clear input
  input.focus();           // return focus to input
});