Reading and Writing Content
JavaScript can read and modify the text content, HTML structure, and attributes of any element in the DOM.
- Read and set element text safely with textContent
- Understand when innerHTML is appropriate and when it is dangerous
- Read, set, and remove attributes with getAttribute / setAttribute / removeAttribute
- Use the dataset API to access custom data attributes
- Toggle CSS classes with classList without touching className directly
Once you have a reference to an element, you can read what is inside it or replace it entirely. The DOM gives you several properties for this — each with a different scope and different trade-offs.
textContent — the safe default
element.textContent reads or writes the plain text content of an element,
including text inside any nested elements. It never parses HTML tags — anything
that looks like a tag is treated as literal text characters.
const heading = document.querySelector('h1');
// Reading
console.log(heading.textContent); // "Welcome to the dashboard"
// Writing
heading.textContent = 'Hello, Priya!';
// The element now reads: Hello, Priya!
// Setting it with user input is safe — tags are escaped automatically
const userInput = '<script>alert("xss")</script>';
heading.textContent = userInput;
// Renders as literal text: <script>alert("xss")</script>Use textContent whenever you are working with text — especially text that
comes from user input. It is the safe default.
innerHTML — powerful, handle with care
element.innerHTML reads or writes the HTML markup inside an element. The
browser parses the string you assign as HTML:
const container = document.querySelector('#card-list');
// Reading — returns the HTML source of the element's children
console.log(container.innerHTML);
// Writing — creates two new <p> elements
container.innerHTML = '<p>First paragraph</p><p>Second paragraph</p>';This is useful when you genuinely need to insert structured HTML. It is dangerous when the content comes from outside your code:
innerHTML with untrusted input is an XSS vector. If an attacker can
control the string you assign to innerHTML, they can inject executable
<script> tags or event-handler attributes. Never do this:
el.innerHTML = userComment — use textContent instead, or pass the input
through a trusted sanitizer.
innerText — the slower cousin
element.innerText behaves similarly to textContent but is CSS-aware: it
respects display: none, normalizes whitespace to match the rendered layout,
and triggers a layout calculation each time it is read. Prefer textContent
for programmatic access — innerText is mainly useful when you need to capture
text exactly as rendered for a copy feature.
Reading and writing attributes
HTML attributes — href, src, disabled, aria-expanded — are accessible
through three methods:
const link = document.querySelector('a.learn-more');
// Read any attribute
const destination = link.getAttribute('href'); // "/docs/intro"
// Set any attribute (both name and value are strings)
link.setAttribute('aria-expanded', 'true');
link.setAttribute('tabindex', '-1');
// Remove an attribute entirely
link.removeAttribute('disabled');setAttribute and removeAttribute work for every attribute, including ARIA
attributes. Prefer them over direct property access (link.href = …) for
anything that is not a standard property with a well-defined IDL attribute —
the property and attribute do not always contain the same value.
The dataset API — custom data attributes
HTML attributes prefixed with data- are
reserved for custom application data. The dataset property gives you clean
camelCase access to them:
<button data-user-id="42" data-action="delete">Remove</button>const btn = document.querySelector('button');
btn.dataset.userId // "42"
btn.dataset.action // "delete"
// Setting
btn.dataset.confirmed = 'true';
// Writes data-confirmed="true" to the elementdata-* attributes are the right place to stash small pieces of metadata that
belong to a specific element — IDs, states, configuration. They keep data close
to the markup without polluting global variables.
Class manipulation with classList
The classList property exposes a DOMTokenList with four essential methods:
const card = document.querySelector('.card');
card.classList.add('highlighted'); // adds the class
card.classList.remove('highlighted'); // removes it
card.classList.toggle('highlighted'); // adds if absent, removes if present
card.classList.contains('highlighted'); // returns true or falseNever manipulate classes by reading and rewriting element.className as a
string. That approach requires you to parse and rebuild a space-separated list
manually, and it is easy to introduce bugs — especially when the element has
multiple classes:
// Fragile — easy to corrupt existing classes
element.className = element.className + ' highlighted';
// Safe — classList handles the list for you
element.classList.add('highlighted');Inline styles — the last resort
You can set a CSS property directly with element.style:
el.style.color = 'red';
el.style.marginTop = '16px'; // camelCase, not kebab-caseUse this sparingly. Inline styles have the highest specificity, making them
hard to override in stylesheets. They also scatter presentation logic through
your JavaScript. In almost every case, toggling a CSS class is the better
approach: define the visual state in your stylesheet, then turn it on or off
from JavaScript with classList.toggle.
Where to go next
You can now read, set, and replace content in the DOM. The next lesson — Event Listeners — covers how to make the page respond when the user actually does something.
Selecting Elements
querySelector and querySelectorAll let you find elements using the same CSS selector syntax you already know — they return live or static node collections.
Event Listeners
Event listeners let JavaScript respond to user actions — clicks, keyboard presses, form submissions — by registering a callback that runs when the event fires.