The DOM (Document Object Model) is a programming interface for HTML documents. When a browser loads a web page, it creates a tree-like representation of the HTML that JavaScript can interact with. This lets you make pages dynamic and interactive.
<!-- HTML -->
<div id="container">
<h1>Hello</h1>
<p>Welcome</p>
</div>
DOM Tree:
div#container
├─ h1
│ └─ "Hello"
└─ p
└─ "Welcome"
Every HTML element becomes a "node" in this tree that JavaScript can find, modify, create, or remove. This is how websites respond to clicks, update content without reloading, and create interactive experiences.

Visual representation of the DOM
<aside> 💡
Info
The browser parses your HTML and creates the DOM automatically. When you use JavaScript to change the DOM, the browser immediately updates what you see on the page - no refresh needed.
</aside>
https://www.youtube.com/watch?v=NO5kUNxGIu0
<button id="myButton">Click me</button>
<script>
// 1. Find the button in the DOM
const button = document.querySelector('#myButton');
// 2. Add interactivity
button.addEventListener('click', function() {
button.textContent = 'Clicked!';
});
</script>
When you click the button, JavaScript modifies the DOM, and the browser updates the display instantly.
<aside> ⌨️
Hands On
Open DevTools Console on any webpage and type: document.querySelector('h1').textContent = 'I changed this!' - watch the heading change instantly. This is DOM manipulation in action.
</aside>
Before you can manipulate elements, you need to find them in the DOM.
Returns the first element that matches the selector:
// By ID
const header = document.querySelector('#main-header');
// By class
const button = document.querySelector('.btn');
// By element type
const paragraph = document.querySelector('p');
// By complex selector
const firstLink = document.querySelector('nav a');
// If no match found, returns null
const missing = document.querySelector('.nonexistent'); // null
Returns a NodeList (like an array) of all matching elements:
// All paragraphs
const paragraphs = document.querySelectorAll('p');
// All elements with class "card"
const cards = document.querySelectorAll('.card');
// All links inside nav
const navLinks = document.querySelectorAll('nav a');
// Loop through results
paragraphs.forEach(function(p) {
console.log(p.textContent);
});
Older method, still commonly used. Only works with IDs:
const container = document.getElementById('container');
// Same as: document.querySelector('#container')
<aside> 💡
Info
querySelector() is more flexible because it accepts any (CSS) selector. Use querySelector() for single elements and querySelectorAll() for multiple elements.
</aside>
// Select first button
const btn = document.querySelector('button');
// Select all list items
const items = document.querySelectorAll('li');
// Select element by ID
const output = document.querySelector('#output');
// Select nested elements
const firstCardTitle = document.querySelector('.card h2');
<aside> ⌨️
Hands On
Create an HTML file with 3 paragraphs, each with class "text". Use querySelectorAll('.text') to select them all and log each one's text content in the console.
</aside>
Once you've selected elements, you can change their content and appearance.
Updates the text inside an element:
const heading = document.querySelector('h1');
// Read current text
console.log(heading.textContent); // "Original Heading"
// Change text
heading.textContent = 'New Heading';
Updates the HTML inside an element:
const container = document.querySelector('#container');
// Read current HTML
console.log(container.innerHTML); // "<p>Hello</p>"
// Change HTML
container.innerHTML = '<h2>New Title</h2><p>New paragraph</p>';
<aside> ⚠️
Warning
Never use innerHTML with user input - it can execute malicious scripts. Always use textContent for user-provided content. Use innerHTML only when you control the HTML content yourself.
</aside>
You can dynamically add or remove elements from the page.
// 1. Create new element
const newParagraph = document.createElement('p');
// 2. Set its content
newParagraph.textContent = 'This is a new paragraph';
// 3. Add classes or styles
newParagraph.classList.add('intro');
// 4. Append to the page
const container = document.querySelector('#container');
container.appendChild(newParagraph);
const element = document.querySelector('.remove-me');
// Remove the element
element.remove();
// Old way (still works)
element.parentElement.removeChild(element);
<aside> 💡
Info
element.remove() is modern and simple. parentElement.removeChild(element) is the older method but still widely supported. Both do the same thing.
</aside>
Event listeners make your page respond to user actions.
Attach functions that run when events occur:
// Syntax
element.addEventListener('eventType', function() {
// Code to run when event happens
});
// Example
const button = document.querySelector('#myButton');
button.addEventListener('click', function() {
console.log('Button was clicked!');
});
Mouse events:
// Click
element.addEventListener('click', function() { });
// Double click
element.addEventListener('dblclick', function() { });
// Mouse enter/leave (for hover effects)
element.addEventListener('mouseenter', function() { });
element.addEventListener('mouseleave', function() { });
Keyboard events:
// Any key pressed
document.addEventListener('keydown', function(event) {
console.log('Key pressed:', event.key);
});
// Specific key
document.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
console.log('Enter was pressed!');
}
});
Form events:
const form = document.querySelector('form');
// Form submitted
form.addEventListener('submit', function(event) {
event.preventDefault(); // Prevent page reload
console.log('Form submitted!');
});
const input = document.querySelector('input');
// Input value changed
input.addEventListener('input', function() {
console.log('Current value:', input.value);
});
Form handling:
const form = document.querySelector('#contact-form');
const message = document.querySelector('#message');
form.addEventListener('submit', function(event) {
event.preventDefault();
// Get form values
const name = document.querySelector('#name').value;
const email = document.querySelector('#email').value;
// Display message
message.textContent = `Thanks, ${name}! We'll contact you at ${email}`;
// Clear form
form.reset();
});
Event listeners receive an event object with information:
button.addEventListener('click', function(event) {
console.log('Clicked element:', event.target);
console.log('Click position:', event.clientX, event.clientY);
});
input.addEventListener('input', function(event) {
console.log('Current value:', event.target.value);
});
Stop default browser actions, for example a form submission. When a form is submitted, the default action is to perform a POST request. However, you might want to perform a different action or let JavaScript handle the form.
// Prevent form submission reload
form.addEventListener('submit', function(event) {
event.preventDefault();
// Handle form with JavaScript instead
});
// Prevent link navigation
link.addEventListener('click', function(event) {
event.preventDefault();
// Do something else instead
});
<aside> 😄
</aside>
1. Select elements once, reuse the reference
// Less efficient - queries DOM every click
button.addEventListener('click', function() {
document.querySelector('#output').textContent = 'Clicked';
});
// Better - query once
const output = document.querySelector('#output');
button.addEventListener('click', function() {
output.textContent = 'Clicked';
});
2. Use textContent for user input
// Unsafe
element.innerHTML = userInput;
// Safe
element.textContent = userInput;
3. Prefer classList over style
// Less maintainable
element.style.backgroundColor = 'red';
element.style.padding = '20px';
// Better
element.classList.add('error');
4. Check if elements exist
const element = document.querySelector('.might-not-exist');
if (element) {
element.textContent = 'Found it!';
}