Code of the Day
JavaScript in the BrowserJavaScript in the Browser

Reading and Writing Content

JavaScript can read and modify the text content, HTML structure, and attributes of any element in the DOM.

Web FoundationsDialect appendices7 min read
Recommended first
By the end of this lesson you will be able to:
  • 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

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 element

data-* 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 false

Never 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-case

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

Finished reading? Mark it complete to track your progress.

On this page