Modifying The Dom
Three ways to change what is on screen, and they are not interchangeable. Here is when to use innerHTML, textContent, and createElement, and why class and for work differently in JavaScript.
In the previous post we covered how to find elements. Once you have a reference to an element, the next question is how to change it. There are several approaches, and picking the wrong one for the situation ranges from slightly inefficient to a security vulnerability.
The Essentials
textContent: Sets or reads the plain text inside an element. HTML tags are treated as literal characters, not markup. Safe from XSS injection.innerHTML: Sets or reads the full HTML string inside an element. Tags are parsed and become real DOM nodes. Never use it with untrusted user input.createElement+appendChild: Builds nodes programmatically without parsing any HTML string. Verbose but explicit and safe.- Attribute naming exceptions:
classin HTML becomesclassNamein JavaScript.forin HTML becomeshtmlFor. Both exist because those words are reserved in JavaScript. - Style property: Every element has a
styleobject. CSS property names use camelCase:fontSize,borderRightColor,backgroundColor.
Reading and Writing Attributes
When you have a reference to a DOM element, you can read or write most of its HTML attributes as JavaScript properties using dot notation.
const link = document.querySelector('a.primary');
// read
console.log(link.href);
console.log(link.hidden);
// write
link.href = 'https://example.com';
link.hidden = false;For 99% of attributes, the property name matches the attribute name. The two exceptions worth memorizing are className and htmlFor.
class is a reserved keyword in JavaScript. You cannot use it as a property name directly, so the DOM API maps it to className:
element.className = 'card active';for is also reserved (used in for loops), so a <label for="email"> element exposes that attribute as htmlFor:
label.htmlFor = 'email';If you have ever used JSX, this is exactly why React uses className and htmlFor too. JSX is still JavaScript, so the same reserved-word constraint applies.
Changing Styles
Every DOM element has a style property that gives you access to its inline styles. CSS property names use camelCase because hyphens are not valid in JavaScript property names:
const box = document.querySelector('.box');
box.style.backgroundColor = '#3b82f6';
box.style.fontSize = '18px';
box.style.borderRightColor = 'red';Note that style only reads and writes inline styles. It does not reflect computed styles from stylesheets. To get the full computed style, use window.getComputedStyle(element).
Three Ways to Change Content
textContent
textContent sets or reads the plain text inside an element. Any HTML tags in the string you assign are escaped and shown as literal characters to the user:
const heading = document.querySelector('h1');
heading.textContent = 'Hello <em>world</em>';
// The user sees: Hello <em>world</em> (literal text, not italic)Use textContent when you are dealing with user-generated content or any input you do not fully control. There is no way to inject HTML through textContent.
innerHTML
innerHTML sets or reads the full HTML string inside an element. The string is parsed by the browser and becomes real DOM nodes:
const container = document.querySelector('.card');
container.innerHTML = '<h2>Title</h2><p>Some text.</p>';
// Two real elements are created inside .cardThis is convenient for generating structured content from a template string. The risk is that if you include untrusted user input in that string, you can accidentally execute malicious scripts. Never concatenate user-provided strings directly into innerHTML.
// Dangerous
container.innerHTML = `<p>Hello, ${userInput}</p>`;
// Safe
const p = document.createElement('p');
p.textContent = `Hello, ${userInput}`;
container.appendChild(p);createElement and appendChild
The most explicit approach is to build the DOM tree node by node:
const list = document.createElement('ul');
const items = ['Espresso', 'Latte', 'Cappuccino'];
items.forEach(name => {
const li = document.createElement('li');
li.textContent = name;
list.appendChild(li);
});
document.querySelector('#menu').appendChild(list);This is more verbose but gives you the most control. Each element is a proper object you can attach event listeners to before it is inserted. There is no HTML string to parse. No injection risk.
One thing to note: appendChild inserts the element as the last child. If you want to insert it at a specific position, use insertBefore or insertAdjacentElement.
Which Approach to Use
The decision depends on the content:
- Static content you wrote yourself:
innerHTMLis fine and reads clearly. - Any content that includes user input:
textContentfor text,createElementfor structure. - Complex trees where you need references to individual nodes:
createElementso you can keep variables pointing to each node. - Large batches of HTML from a server:
innerHTMLon a container you control, or useDOMParserto parse safely.
Event Listeners
Any DOM element can listen for events using addEventListener:
const button = document.querySelector('button.submit');
button.addEventListener('click', (event) => {
event.preventDefault();
console.log('Clicked');
});The event name is a string. The handler is a function that receives an event object with metadata about what happened.
You can attach multiple handlers to the same event on the same element. If you use the onevent shorthand (element.onclick = fn), assigning a new function replaces the previous one. addEventListener does not have that limitation.
Further Reading and Watching
- MDN: Element.innerHTML - Full reference including the security warning about using it with untrusted content.
- MDN: Node.appendChild - Reference for
appendChildand the broaderNodeinterface it belongs to.
Video:
- JavaScript DOM Crash Course - Part 2 by Traversy Media. Covers DOM manipulation including
innerHTML,createElement, and event listeners with practical examples.
Practice what you just read.
Keep reading