🎯 Lesson 14: Selecting & Modifying Elements
Now that you understand the DOM tree, it's time to reach in and grab elements — then change their text, classes, styles, and attributes with JavaScript.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Select single elements with
querySelectorandgetElementById - Select multiple elements with
querySelectorAll - Read and change element text with
textContentandinnerHTML - Add, remove, and toggle CSS classes with
classList - Modify inline styles with the
styleproperty - Get and set HTML attributes with
getAttributeandsetAttribute - Work with data attributes (
data-*)
Estimated Time: 50 minutes
Project: Build a dynamic profile card editor
📑 In This Lesson
Selecting a Single Element
querySelector() — The Modern Standard
querySelector finds the first element matching a CSS selector. If you know CSS selectors, you already know how to use it.
// By tag name
const heading = document.querySelector("h1");
// By class name (prefix with .)
const card = document.querySelector(".card");
// By ID (prefix with #)
const nav = document.querySelector("#main-nav");
// By attribute
const link = document.querySelector('a[href="#intro"]');
// Complex selectors work too
const firstItem = document.querySelector("ul.todo-list > li:first-child");
// Returns null if nothing matches
const nope = document.querySelector(".nonexistent");
console.log(nope); // null
getElementById() — The Classic
// Only selects by ID — no # prefix needed
const nav = document.getElementById("main-nav");
// Equivalent to:
const nav2 = document.querySelector("#main-nav");
💡 querySelector vs. getElementById
Use querySelector for everything — it's more flexible since it accepts any CSS selector. getElementById is slightly faster for ID lookups, but the difference is negligible in practice. Consistency wins.
Selecting Multiple Elements
querySelectorAll()
querySelectorAll returns all matching elements as a NodeList. You can loop over it with forEach or for...of.
// Get all paragraphs
const paragraphs = document.querySelectorAll("p");
console.log(paragraphs.length); // Number of <p> elements
// Loop with forEach
paragraphs.forEach(p => {
console.log(p.textContent);
});
// Loop with for...of
for (const p of paragraphs) {
p.style.color = "blue";
}
// Get all elements with a specific class
const cards = document.querySelectorAll(".card");
// Complex selectors
const activeLinks = document.querySelectorAll("nav a.active");
⚠️ NodeList Is Not an Array
A NodeList supports forEach and for...of, but not array methods like map, filter, or reduce. Convert it to an array if you need those.
const items = document.querySelectorAll("li");
// ❌ items.map(...) — TypeError!
// ✅ Convert to array first
const itemArray = [...items]; // Spread into array
const texts = itemArray.map(item => item.textContent);
// Or use Array.from()
const texts2 = Array.from(items).map(item => item.textContent);
Scoped Queries
You can call querySelector on any element, not just document. This limits the search to that element's descendants.
const sidebar = document.querySelector(".sidebar");
// Only search within the sidebar
const sidebarLinks = sidebar.querySelectorAll("a");
const sidebarTitle = sidebar.querySelector("h2");
or null"] C --> F["All matches
(NodeList)"] D --> G["By ID only
or null"] style B fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style C fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style D fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b
Changing Text Content
textContent — Safe and Simple
textContent gets or sets the plain text inside an element. It's the safest option because it treats everything as text (no HTML parsing).
const heading = document.querySelector("h1");
// Read the text
console.log(heading.textContent); // "Hello World"
// Change the text
heading.textContent = "New Heading!";
// HTML tags are treated as plain text (safe!)
heading.textContent = "<em>Not italic</em>";
// Displays literally: <em>Not italic</em>
innerHTML — Parses HTML
innerHTML gets or sets the HTML content inside an element. It parses HTML tags, which is powerful but has security implications.
const container = document.querySelector(".content");
// Read the HTML
console.log(container.innerHTML); // "<p>Hello</p><p>World</p>"
// Set new HTML — tags are parsed and rendered
container.innerHTML = "<h2>New Section</h2><p>With a paragraph.</p>";
// Add to existing HTML
container.innerHTML += "<p>Another paragraph</p>";
❌ Never Use innerHTML with User Input
If you insert user-provided text with innerHTML, a malicious user could inject JavaScript code. This is called a Cross-Site Scripting (XSS) attack. Always use textContent for user data.
const userInput = '<img src="x" onerror="alert(\'hacked!\')">';
// ❌ DANGEROUS — this would execute the script
// element.innerHTML = userInput;
// ✅ SAFE — treats everything as plain text
element.textContent = userInput;
| Property | Parses HTML? | Safe for User Input? | Use When |
|---|---|---|---|
textContent | No | Yes | Setting or reading plain text |
innerHTML | Yes | No! | Inserting HTML you control |
innerText | No | Yes | Reading visible text only (slower) |
Working with Classes
The classList property provides methods to add, remove, toggle, and check CSS classes. This is how you change an element's appearance dynamically.
const card = document.querySelector(".card");
// Add a class
card.classList.add("highlighted");
// <div class="card highlighted">
// Remove a class
card.classList.remove("highlighted");
// <div class="card">
// Toggle — add if missing, remove if present
card.classList.toggle("active");
// If it had "active" → removes it. If it didn't → adds it.
// Check if a class exists
if (card.classList.contains("card")) {
console.log("It's a card!"); // true
}
// Add multiple classes at once
card.classList.add("featured", "new", "sale");
// Remove multiple classes
card.classList.remove("new", "sale");
// Replace one class with another
card.classList.replace("featured", "standard");
Practical: Toggle Dark Mode
const toggleButton = document.querySelector("#theme-toggle");
toggleButton.addEventListener("click", () => {
document.body.classList.toggle("dark-mode");
});
// In CSS:
// .dark-mode { background: #1a1a2e; color: #eee; }
⚠️ classList vs. className
The older className property returns all classes as a single string. Modifying it replaces all classes. Use classList instead — it's safer and more precise.
// ❌ className replaces everything
card.className = "active"; // Removes "card" class too!
// ✅ classList adds/removes individually
card.classList.add("active"); // Keeps existing classes
Modifying Inline Styles
The style property lets you set inline CSS on an element. Property names use camelCase instead of kebab-case.
const box = document.querySelector(".box");
// Set individual styles
box.style.backgroundColor = "#3b82f6";
box.style.color = "white";
box.style.padding = "1rem";
box.style.borderRadius = "8px";
box.style.fontSize = "18px";
// Read a style
console.log(box.style.color); // "white"
// Remove a style (set to empty string)
box.style.backgroundColor = "";
| CSS Property | JavaScript style Property |
|---|---|
background-color | backgroundColor |
font-size | fontSize |
border-radius | borderRadius |
z-index | zIndex |
margin-top | marginTop |
Getting Computed Styles
element.style only reads inline styles. To see the actual applied styles (including CSS from stylesheets), use getComputedStyle.
const heading = document.querySelector("h1");
// Only reads inline styles (often empty)
console.log(heading.style.fontSize); // "" (not set inline)
// Reads the ACTUAL computed style (from CSS + inline)
const computed = getComputedStyle(heading);
console.log(computed.fontSize); // "32px" (from stylesheet)
console.log(computed.color); // "rgb(30, 41, 59)"
✅ Prefer Classes Over Inline Styles
For most visual changes, toggle a CSS class instead of setting inline styles. Classes keep your styling in CSS where it belongs. Use inline styles only for dynamic values (like calculated positions or user-chosen colors).
// ✅ Better — toggle a class
element.classList.add("highlighted");
// ❌ Avoid for static styling
element.style.backgroundColor = "yellow";
element.style.fontWeight = "bold";
element.style.border = "2px solid orange";
Attributes & Data Attributes
Getting and Setting Attributes
const link = document.querySelector("a");
// Read attributes
console.log(link.getAttribute("href")); // "https://example.com"
console.log(link.getAttribute("target")); // "_blank" or null
// Set attributes
link.setAttribute("href", "https://new-url.com");
link.setAttribute("title", "Visit this link");
// Check if an attribute exists
console.log(link.hasAttribute("target")); // true or false
// Remove an attribute
link.removeAttribute("title");
Common Properties vs. getAttribute
Many attributes have direct properties on the element. These are usually more convenient.
const img = document.querySelector("img");
// Direct properties (preferred for common attributes)
img.src = "new-image.jpg";
img.alt = "A beautiful sunset";
img.width = 400;
const link = document.querySelector("a");
link.href = "https://example.com";
const input = document.querySelector("input");
input.value = "Hello";
input.disabled = true;
input.placeholder = "Enter your name";
Data Attributes (data-*)
Custom data-* attributes let you store extra information on HTML elements. Access them through the dataset property.
<!-- In your HTML -->
<button data-action="delete" data-item-id="42" data-confirm="true">
Delete Item
</button>
const btn = document.querySelector("button");
// Read data attributes via dataset
console.log(btn.dataset.action); // "delete"
console.log(btn.dataset.itemId); // "42" (camelCase!)
console.log(btn.dataset.confirm); // "true" (always a string)
// Set data attributes
btn.dataset.status = "pending";
// Adds: data-status="pending" to the HTML
// Delete a data attribute
delete btn.dataset.confirm;
💡 data-* Naming Convention
HTML uses kebab-case (data-item-id), but JavaScript's dataset converts to camelCase (dataset.itemId). The conversion works both ways automatically.
innerHTML"] C --> G["classList.add()
.remove()
.toggle()"] D --> H["style.property
getComputedStyle()"] E --> I["getAttribute()
setAttribute()
dataset"] style B fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style C fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style D fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style E fill:#fce7f3,stroke:#ec4899,stroke-width:2px,color:#1e293b
Hands-on Exercise
🏋️ Exercise: Dynamic Profile Card Editor
Objective: Use DOM selection and modification to build an interactive profile card. Create a new HTML file or use the console on any page.
<!-- Save this as profile.html and open in a browser -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Profile Card Editor</title>
<style>
body { font-family: sans-serif; padding: 2rem; background: #f1f5f9; }
.profile-card {
max-width: 400px; padding: 2rem; background: white;
border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.profile-card h2 { margin-top: 0; }
.highlight { border: 3px solid #f59e0b; }
.dark { background: #1e293b; color: #e2e8f0; }
.hidden { display: none; }
</style>
</head>
<body>
<div class="profile-card" id="card"
data-user-id="101" data-role="developer">
<h2 id="name">Jane Doe</h2>
<p id="bio">Full-stack developer who loves JavaScript.</p>
<p id="location">📍 Austin, TX</p>
<a id="website" href="https://example.com">Visit Website</a>
</div>
<script>
// TODO: Complete each task using DOM methods
// 1. Change the name to your name
// 2. Update the bio text
// 3. Change the location
// 4. Update the website link (both text and href)
// 5. Add the "highlight" class to the card
// 6. Change the card's background color to a light blue
// 7. Read and log the card's data-user-id
// 8. Add a new data attribute: data-status="active"
// 9. Toggle the "dark" class on and off the card
// 10. Bonus: Create a function that takes a profile object
// and updates all fields at once
// updateProfile({ name: "Ray", bio: "...", location: "...", website: "..." })
</script>
</body>
</html>
💡 Hint
1-4: Use querySelector or getElementById to select, then set textContent for text and href for links.
5: Use classList.add().
6: Use style.backgroundColor.
7-8: Use dataset.userId to read and dataset.status = "active" to set.
9: Use classList.toggle("dark").
10: Write a function that takes an object parameter, destructures it, and sets each field.
✅ Solution
// 1. Change the name
const nameEl = document.querySelector("#name");
nameEl.textContent = "Ray De La Paz";
// 2. Update the bio
document.querySelector("#bio").textContent =
"Technical trainer and course developer who loves building things.";
// 3. Change the location
document.querySelector("#location").textContent = "📍 Henderson, NV";
// 4. Update the website
const website = document.querySelector("#website");
website.textContent = "Visit Portfolio";
website.href = "https://rays-home.netlify.app/";
// 5. Add the highlight class
const card = document.querySelector("#card");
card.classList.add("highlight");
// 6. Change background color
card.style.backgroundColor = "#eff6ff";
// 7. Read data-user-id
console.log(card.dataset.userId); // "101"
// 8. Add data-status
card.dataset.status = "active";
// Card now has: data-status="active"
// 9. Toggle dark mode
card.classList.toggle("dark");
// Call again to toggle off:
// card.classList.toggle("dark");
// 10. Bonus: Update all fields at once
function updateProfile({ name, bio, location, website }) {
if (name) document.querySelector("#name").textContent = name;
if (bio) document.querySelector("#bio").textContent = bio;
if (location) document.querySelector("#location").textContent = `📍 ${location}`;
if (website) {
const el = document.querySelector("#website");
el.href = website;
el.textContent = "Visit Website";
}
}
updateProfile({
name: "Alice Smith",
bio: "UX designer and coffee enthusiast.",
location: "Portland, OR",
website: "https://alice.dev"
});
🎯 Quick Quiz
Question 1: What does querySelector return if no element matches?
Question 2: Why should you avoid innerHTML with user input?
Question 3: What method adds a CSS class without removing existing ones?
Question 4: How do you access data-item-id in JavaScript?
Summary
🎉 Key Takeaways
querySelectorandquerySelectorAllfind elements using CSS selectorstextContentsafely reads/writes plain text;innerHTMLparses HTML (avoid with user data!)classListmethods (add,remove,toggle,contains) manage CSS classes preciselystylesets inline CSS using camelCase property names- Prefer toggling classes over setting inline styles for visual changes
datasetaccessesdata-*attributes with automatic kebab-to-camelCase conversiongetComputedStylereads the actual rendered styles, not just inline ones- Convert
NodeListto an array with[...nodeList]to usemap/filter
📚 Additional Resources
- MDN — querySelector()
- MDN — classList
- MDN — Using Data Attributes
- javascript.info — Searching the DOM
🚀 What's Next?
You can now select and modify elements — but how do you respond when a user clicks a button, submits a form, or presses a key? The next lesson covers events and event listeners, the mechanism that makes web pages truly interactive.