Reading Was Just the Beginning
I could read any element on the page. I could grab its text, check its attributes, inspect its classes. But all of that was observation. I hadn't actually changed anything yet.
The Robot ID Card still said "Offline." My robot was running. I wanted to fix that — and I wanted to do it with JavaScript, not by editing the HTML file.
That's when I learned the most important thing about the DOM: JavaScript can modify the page without reloading it. This is what makes the web interactive.
My First Instinct Was innerHTML
I'd seen innerHTML before. It seemed like the obvious choice — I wanted to change what was inside an element, and here was a property called inner-HTML. Perfect, right?
const statusMessage = document.querySelector('#status-message');
statusMessage.innerHTML = 'Online';
// It worked! The card now said "Online" I was so proud of myself. The status changed right there on the page, no reload needed.
It worked perfectly. I leaned back in my chair feeling like a real developer. Then Prof. Teeters walked over.
Prof. Teeters and the XSS Question
Prof. Teeters looked at my code and asked one question that changed everything:
Prof. Teeters:
"That works when you control the text. But what if that string came from a user? What if someone typed it into a form field, and your code put it on the page with innerHTML?"
I didn't see the problem at first. Then Prof. Teeters showed me what a malicious user might type:
const userInput = '<img src="x" onerror="alert('hacked!')">';
statusMessage.innerHTML = userInput;
// → Browser creates an <img> element
// → The image fails to load (src="x" doesn't exist)
// → onerror fires — attacker's code runs on YOUR page innerHTML parses the string as HTML. That means the browser will create real elements from it — including elements that execute code.
This is called Cross-Site Scripting (XSS). The attacker injects their code into your page, and your page runs it as if you wrote it. They could steal cookies, redirect users, or modify what the page displays.
I stared at my innocent-looking innerHTML = 'Online' line. The text was fine this time. But the habit was dangerous.
The Safe Way — textContent
Prof. Teeters showed me the safe alternative: textContent.
const statusMessage = document.querySelector('#status-message');
statusMessage.textContent = 'Online';
// Same result — but SAFE textContent treats everything as plain text. It never parses HTML.
Here's what happens if someone tries the same attack with textContent:
const userInput = '<img src="x" onerror="alert('hacked!')">';
statusMessage.textContent = userInput;
// → Displays the literal text: <img src="x" onerror="alert('hacked!')">
// → No element created, no code executed, no vulnerability The browser displays the angle brackets as literal characters. No HTML is parsed. No elements are created. No scripts run.
🟠 The Rule I Follow Now:
If I'm putting text into an element, I use textContent. Every time. It's the default safe choice.
Changing Classes — classList
Next I wanted to add a CSS class to the status message — an .online class that would change the color. My first instincts were all wrong.
Wrong: element.class
element.class doesn't work. In JavaScript, class is a reserved word (it's used for ES6 classes). The DOM uses className instead — but even that has problems.
Wrong: setAttribute('class', ...)
setAttribute('class', 'online') overwrites all existing classes. If the element had class="status-row", it would lose that class entirely.
Wrong: className += ' online'
String concatenation is fragile. Forgetting the leading space creates "status-rowonline". And removing a class later means messy string manipulation.
Right: classList
classList gives me clean methods for working with classes. It handles spacing, duplicates, and toggling automatically.
const status = document.querySelector('#status-message');
status.classList.add('online'); // Add a class
status.classList.remove('offline'); // Remove a class
status.classList.toggle('active'); // Add if missing, remove if present
status.classList.contains('online'); // Returns true or false classList is the right tool for working with CSS classes. It handles everything cleanly.
Changing Attributes — setAttribute
The Robot ID Card had a data-status attribute set to "offline". I wanted to update it to "online" to match the text I'd changed.
const status = document.querySelector('#status-message');
// Read the current value
console.log(status.getAttribute('data-status')); // "offline"
// Update it
status.setAttribute('data-status', 'online');
// Verify
console.log(status.getAttribute('data-status')); // "online" setAttribute is great for data-* attributes, aria-* attributes, and other custom attributes.
Important: setAttribute('class', 'newclass') overwrites the entire class attribute. That's why classList exists — it adds and removes individual classes without destroying the others.
The Breakthrough
Prof. Teeters:
"That's the power of the DOM. The page is never finished — it's always ready to change."
That hit me hard. Every website I'd ever used — every time a notification badge updated, every time a button changed color on hover, every time a form showed an error message — that was JavaScript modifying the DOM. The page wasn't a static document. It was a living thing, always ready to respond.
And now I could do it too. I could change text, toggle classes, update attributes — all without reloading. The page was mine to modify.
Inline Styles — And Why Prof. Teeters Discouraged Them
I also learned I could change CSS styles directly with JavaScript:
const energyBar = document.querySelector('#energy-bar');
// Set inline styles
energyBar.style.width = '75%';
energyBar.style.backgroundColor = 'hsl(120, 60%, 45%)'; Notice backgroundColor — CSS properties with hyphens become camelCase in JavaScript.
This works, but Prof. Teeters discouraged it for most situations. Inline styles are hard to maintain, hard to override, and they mix behavior with presentation.
🟠 What I Learned:
Instead of setting style.color or style.backgroundColor, I toggle a CSS class that has the styles I want. The styles stay in the stylesheet where they belong, and my JavaScript stays focused on behavior.
// Instead of: energyBar.style.backgroundColor = 'hsl(120, 60%, 45%)';
// Do this:
energyBar.classList.add('energy-high');
// The .energy-high class in CSS handles the color
HAP's Confession:
- I reached for
innerHTMLfirst because the name made sense to me. It took Prof. Teeters showing me an XSS attack to understand whytextContentis the safe default. - I tried
element.class = 'online'and couldn't figure out why nothing happened. Turns outclassis a reserved word in JavaScript — the DOM usesclassListinstead. - I set
style.color = 'hsl(0, 80%, 50%)'directly in JavaScript to make the status red. It worked, but the next day I couldn't find where the color was coming from because it wasn't in my CSS file. Now I toggle classes instead.
🟠 Try It Yourself:
I built a DOM Modifier demo where you can change text, toggle classes, update attributes, and even see what innerHTML does with dangerous input (safely!). It's the best way to feel how these methods work.
🎓 DOM Modification Quick Reference
textContent Is the Safe Default
Use textContent to set text in an element. It never parses HTML, so it can't create an XSS vulnerability.
Never innerHTML With User Input
innerHTML parses strings as HTML. If that string comes from a user, an attacker can inject scripts that run on your page.
classList for Classes
Use classList.add(), .remove(), .toggle(), and .contains(). Never overwrite the class attribute directly.
setAttribute Overwrites
setAttribute replaces the entire attribute value. It's perfect for data-* and aria-* attributes, but use classList for classes.
Toggle Classes, Not Inline Styles
Instead of setting style.color in JavaScript, toggle a CSS class. Keep styles in your stylesheet where they're maintainable.
The Page Is Never Finished
The DOM is always ready to change. Every interactive feature on the web — notifications, form validation, live updates — is JavaScript modifying the DOM.
What's Next?
I could find elements, read them, and change them. But what if the element I needed didn't exist yet? What if I wanted to add a whole new row to the Robot ID Card — one that wasn't in the original HTML?
That's when Prof. Teeters introduced me to createElement and appendChild. The DOM wasn't a fixed document anymore — I could build new pieces of it from scratch. 🟠