Modifying Elements
Change text, HTML, attributes, classes, and styles on existing DOM elements.
1. Introduction
Once you can select elements, the next skill is modifying them. This is where pages become truly dynamic: updating text when data loads, toggling a class to apply a CSS style, changing an image source, or updating a button's disabled state.
This lesson covers five categories of modification:
- Content —
textContent,innerHTML - Attributes —
getAttribute,setAttribute,removeAttribute,dataset - Classes —
classList.add/remove/toggle/contains - Inline styles —
styleproperty - Form values —
value,checked,disabled
2. Theory
2.1 Changing text content
const heading = document.querySelector('h1');
// textContent — plain text (safe, no HTML parsing)
heading.textContent = 'New Heading';
console.log(heading.textContent); // 'New Heading'
// innerText — similar to textContent but respects CSS visibility
// (textContent is usually preferred — faster and more predictable)
// innerHTML — parses as HTML (use carefully!)
heading.innerHTML = 'Hello <em>World</em>';
// The heading now renders with italic "World"
innerHTML to user-supplied content without sanitising. It can execute malicious scripts (XSS attack). Use textContent for user data.2.2 Attributes — getAttribute, setAttribute, removeAttribute
const img = document.querySelector('img');
const link = document.querySelector('a');
const btn = document.querySelector('button');
// Read an attribute
img.getAttribute('src'); // '/images/cat.jpg'
link.getAttribute('href'); // 'https://...'
// Set an attribute
img.setAttribute('src', '/images/dog.jpg');
img.setAttribute('alt', 'A dog playing');
link.setAttribute('href', 'https://example.com');
// Remove an attribute
btn.removeAttribute('disabled');
// Check if attribute exists
img.hasAttribute('alt'); // true or false
Direct property access (usually preferred)
Many attributes are directly accessible as element properties — cleaner than getAttribute/setAttribute:
img.src = '/images/dog.jpg'; // same as setAttribute('src', ...)
img.alt = 'A dog';
link.href = 'https://example.com';
input.placeholder = 'Enter name...';
btn.disabled = true; // disables the button
btn.disabled = false; // enables it
2.3 data-* attributes (dataset)
Custom HTML attributes starting with data- are accessible through the dataset property:
<!-- HTML -->
<button data-id="42" data-action="delete">Delete</button>
const btn = document.querySelector('button');
// Read
btn.dataset.id; // '42' (always a string)
btn.dataset.action; // 'delete'
// Write
btn.dataset.id = '99';
btn.dataset.newProp = 'hello'; // adds data-new-prop="hello"
// Note: camelCase in JS maps to kebab-case in HTML
// dataset.userId → data-user-id in HTML
2.4 classList — working with CSS classes
The preferred way to manage an element's classes. Never overwrite className directly — that destroys existing classes.
const card = document.querySelector('.card');
card.classList.add('featured'); // adds 'featured' class
card.classList.remove('hidden'); // removes 'hidden' class
card.classList.toggle('active'); // adds if absent, removes if present
card.classList.contains('featured'); // true or false
card.classList.replace('old', 'new'); // replace one class with another
// Add multiple at once
card.classList.add('highlight', 'bold');
// Read all classes
console.log(card.className); // 'card featured highlight bold'
console.log([...card.classList]); // ['card', 'featured', 'highlight', 'bold']
2.5 Inline styles
Set CSS directly on an element using the style property. CSS property names become camelCase:
const box = document.querySelector('.box');
box.style.backgroundColor = 'royalblue'; // background-color
box.style.fontSize = '1.5rem'; // font-size
box.style.borderRadius = '8px';
box.style.display = 'none'; // hide
box.style.display = ''; // restore (empty string removes inline style)
// Read computed (final) styles (after CSS is applied)
const styles = getComputedStyle(box);
console.log(styles.fontSize); // '24px' (resolved value)
console.log(styles.backgroundColor); // 'rgb(65, 105, 225)'
Prefer toggling CSS classes over setting inline styles — it keeps styling in CSS where it belongs.
2.6 Form element properties
const input = document.querySelector('#username');
const checkbox = document.querySelector('#agree');
const select = document.querySelector('#country');
const textarea = document.querySelector('#message');
// Read values
input.value; // text typed by user
checkbox.checked; // true or false
select.value; // value of selected option
textarea.value; // multi-line text
// Set values
input.value = 'Alice';
checkbox.checked = true;
select.value = 'CA'; // selects the option with value="CA"
// Disable/enable
input.disabled = true;
input.disabled = false;
// Focus
input.focus();
input.blur();
input.select(); // select all text in the input
2.7 Reading vs writing
Most DOM properties work in both directions:
// Read
const text = heading.textContent;
// Write
heading.textContent = 'New text';
// Read
const src = img.src;
// Write
img.src = 'new-image.jpg';
3. Real World Example
// Dark mode toggle
const toggle = document.querySelector('#theme-toggle');
const html = document.documentElement; // <html> element
toggle.addEventListener('click', () => {
html.classList.toggle('dark');
toggle.textContent = html.classList.contains('dark') ? 'Light Mode' : 'Dark Mode';
localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light');
});
// Restore on page load
if (localStorage.getItem('theme') === 'dark') {
html.classList.add('dark');
toggle.textContent = 'Light Mode';
}
// Form character counter
const bio = document.querySelector('#bio');
const count = document.querySelector('#char-count');
const MAX = 160;
bio.addEventListener('input', () => {
const remaining = MAX - bio.value.length;
count.textContent = remaining + ' characters remaining';
count.style.color = remaining < 20 ? 'crimson' : '';
});
4. Code Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Modifying Elements Demo</title>
<style>
.card { border: 2px solid #ddd; padding: 1rem; border-radius: 8px; max-width: 300px; }
.highlighted { border-color: royalblue; background: #eff6ff; }
.hidden { display: none; }
</style>
</head>
<body>
<div class="card" id="profile-card" data-user-id="42">
<img id="avatar" src="avatar-placeholder.png" alt="User avatar" width="60">
<h2 id="user-name">Loading...</h2>
<p id="user-bio"></p>
<button id="follow-btn">Follow</button>
</div>
<button id="load-btn">Load User</button>
<button id="toggle-btn">Toggle Highlight</button>
<script>
const card = document.querySelector('#profile-card');
const nameEl = document.querySelector('#user-name');
const bioEl = document.querySelector('#user-bio');
const avatarEl = document.querySelector('#avatar');
const followBtn = document.querySelector('#follow-btn');
// Simulate loading user data
document.querySelector('#load-btn').addEventListener('click', () => {
// 1. textContent — safe for user-generated text
nameEl.textContent = 'Alice Johnson';
// 2. innerHTML — safe here (controlled content)
bioEl.innerHTML = 'Frontend developer. Loves <em>CSS Grid</em> and coffee.';
// 3. Attributes
avatarEl.src = 'https://i.pravatar.cc/60?img=1';
avatarEl.alt = 'Alice Johnson avatar';
// 4. dataset
console.log('User ID:', card.dataset.userId); // '42'
});
// 5. classList toggle
document.querySelector('#toggle-btn').addEventListener('click', () => {
card.classList.toggle('highlighted');
});
// 6. Toggle follow button
let following = false;
followBtn.addEventListener('click', () => {
following = !following;
followBtn.textContent = following ? 'Unfollow' : 'Follow';
followBtn.style.background = following ? '#e63946' : '';
followBtn.style.color = following ? 'white' : '';
});
</script>
</body>
</html>
5. Code Breakdown
textContent vs innerHTML choice
nameEl.textContent = 'Alice Johnson' — user name from an API should use textContent to prevent XSS if the name ever contains HTML characters. bioEl.innerHTML = '...' is used here because the content is controlled by the developer (not user-supplied) and needs to render an <em> tag.
avatarEl.src = ...
Directly setting the src property is equivalent to setAttribute('src', ...) but shorter. The browser immediately starts downloading the new image.
card.dataset.userId
Reads the data-user-id="42" attribute as a string. The HTML attribute data-user-id maps to dataset.userId (kebab-case → camelCase).
classList.toggle('highlighted')
Adds the class if absent, removes it if present — one line replaces an if/else. The CSS defines what "highlighted" looks like; JS just switches the class on and off.
followBtn.style.background = ''
Setting a style property to an empty string removes that inline style, reverting to the stylesheet value. This is the correct way to "undo" a style set by JavaScript.
6. Common Mistakes
Mistake 1 — Setting innerHTML from user input (XSS)
// DANGEROUS — user types '<img src=x onerror=alert(1)>'
el.innerHTML = userInput; // executes the onerror script!
// Safe — treats everything as plain text
el.textContent = userInput;
Mistake 2 — Overwriting className instead of using classList
// Bad — removes ALL existing classes
el.className = 'active'; // 'card featured' → 'active'
// Good — just adds the new class
el.classList.add('active'); // 'card featured' → 'card featured active'
Mistake 3 — CSS property names in camelCase confusion
// Bad — hyphenated names don't work
el.style.background-color = 'red'; // SyntaxError
// Good — camelCase
el.style.backgroundColor = 'red';
Mistake 4 — Reading style before element is rendered
// style.X only returns values SET inline, not CSS stylesheet values
el.style.fontSize; // '' if no inline style, even if CSS sets it to 20px
// getComputedStyle() reads the final resolved value
getComputedStyle(el).fontSize; // '20px'
Mistake 5 — dataset values are always strings
btn.dataset.count = '5';
btn.dataset.count + 1; // '51' — string concatenation!
// Fix: convert first
Number(btn.dataset.count) + 1; // 6
7. Best Practices
- Use textContent by default — switch to innerHTML only when you need to render HTML markup.
- Never innerHTML user data without sanitising (use DOMPurify or a framework).
- Toggle classes, not inline styles — keeps styling logic in CSS where it belongs.
- Use classList.toggle/add/remove — never overwrite
className. - Use direct property access (
img.src) for common attributes — cleaner than getAttribute/setAttribute. - Use dataset for custom data attributes — cleaner than arbitrary custom attributes.
- Remember dataset values are strings — convert numbers and booleans when reading.
- Set style.property = '' (empty string) to remove an inline style, not
delete.
8. Practice Exercise
- Create a page with a paragraph, an image, a link, and a button.
- Add JavaScript that does the following when buttons are clicked:
- "Change Text" — updates the paragraph text content
- "Change Image" — updates the image src and alt
- "Toggle Class" — toggles a 'highlighted' class on the paragraph
- "Disable Button" — sets the button's disabled property to true
- Build a "Profile Editor": name input + bio textarea + "Save" button. On save, update display elements below the form with the entered values. Use
textContentfor the name andinnerHTML(after basic sanitising) for the bio.
9. Assignment
Build a "Product Card Editor."
- Create a product card with: image, title, price, description, and a "Sale" badge (hidden by default).
- Build a form with inputs for: title, price, description, image URL, and a "On Sale?" checkbox.
- On form submit, update the card:
- Set the card title using
textContent - Set the price (format as "$X.XX")
- Set the description
- Update the image src and alt
- Show/hide the sale badge using
classList.toggle
- Set the card title using
- The "Sale" badge should also reduce the displayed price by 20% when checked.
- Add a "Reset" button that restores original values.
Deliverable: One HTML file.
10. Interview Questions
- What is the difference between textContent and innerHTML?
textContent sets/gets plain text — HTML tags are treated as literals. innerHTML parses the string as HTML markup. Use textContent for user-generated content to prevent XSS. - How do you add a CSS class to an element without removing its existing classes?
Use element.classList.add('new-class'). Never use element.className = 'new-class' — that replaces all existing classes. - What does classList.toggle() do?
It adds the class if it is absent, removes it if it is present. A one-liner replacement for an if/else check. Optionally accepts a second boolean argument to force add (true) or remove (false). - How do you read a CSS value that was set in a stylesheet (not inline)?
Use getComputedStyle(element).propertyName. element.style.property only returns inline styles; it returns empty string for stylesheet-applied styles. - What is the dataset property?
A DOMStringMap of all data-* attributes on an element. data-user-id becomes dataset.userId. Values are always strings — convert numbers and booleans when reading. - How do you remove an inline style set by JavaScript?
Set the property to an empty string: element.style.backgroundColor = ''. This removes the inline style and allows the stylesheet value to apply again.
11. Additional Resources
- MDN — Element.classList
- MDN — HTMLElement.dataset
- MDN — HTMLElement.style
- MDN — Window.getComputedStyle()
- javascript.info — Modifying the document
- OWASP — Cross Site Scripting (XSS) — understand the innerHTML risk