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.

May 1, 20264 min read3 / 12

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

  1. textContent: Sets or reads the plain text inside an element. HTML tags are treated as literal characters, not markup. Safe from XSS injection.
  2. 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.
  3. createElement + appendChild: Builds nodes programmatically without parsing any HTML string. Verbose but explicit and safe.
  4. Attribute naming exceptions: class in HTML becomes className in JavaScript. for in HTML becomes htmlFor. Both exist because those words are reserved in JavaScript.
  5. Style property: Every element has a style object. 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.

JavaScript
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:

JavaScript
element.className = 'card active';

for is also reserved (used in for loops), so a <label for="email"> element exposes that attribute as htmlFor:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
const container = document.querySelector('.card'); container.innerHTML = '<h2>Title</h2><p>Some text.</p>'; // Two real elements are created inside .card

This 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.

JavaScript
// 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:

JavaScript
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: innerHTML is fine and reads clearly.
  • Any content that includes user input: textContent for text, createElement for structure.
  • Complex trees where you need references to individual nodes: createElement so you can keep variables pointing to each node.
  • Large batches of HTML from a server: innerHTML on a container you control, or use DOMParser to parse safely.

Event Listeners

Any DOM element can listen for events using addEventListener:

JavaScript
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

Video:

Practice what you just read.

innerHTML vs createElement: Safety and Control
1 exercise