JavaScript • Web Programming (Browser)

Reading & Updating DOM — Read and update text, attributes, and classes

Learn how JavaScript reads and changes the live content, attributes, and styling of any web page element.

JavaScript emblem with code brackets, braces, and a code file.
Inside this lesson 10 sections 5 questions 10 figures

SECTION 1

Historical Context & Motivation

In the earliest days of the web, pages were completely static. A server sent an HTML file to your browser, the browser displayed it, and that was that — no interactivity, no dynamic updates. If you wanted to change anything on the page, you had to reload the entire thing from the server. This was fine when the web was mostly academic documents, but as the internet grew into a platform for applications and commerce, developers needed a way to manipulate page content on the fly.

The answer was the Document Object Model (DOM) — a programming interface that lets JavaScript reach into an HTML page and read or change any part of it while the user is still looking at it. Understanding how the DOM evolved helps you appreciate why reading and updating text, attributes, and classes is so central to modern web development.

1995 • JavaScript Is Born

Brendan Eich creates JavaScript at Netscape in just ten days. For the first time, browsers can run small scripts that respond to user actions and modify basic page properties.

1998 • DOM Level 1 Standard

The W3C publishes the first official DOM specification, defining a tree-based model for HTML documents. Methods like getElementById() give developers a standard way to locate elements across all browsers.

2004 • Ajax & Dynamic Pages

Google Maps and Gmail demonstrate that JavaScript can update parts of a page without a full reload, popularizing DOM manipulation as a core skill for web developers.

2009 • querySelector Arrives

Modern browsers adopt querySelector() and querySelectorAll(), letting developers select elements with CSS-style selectors — far more flexible than older methods.

2015+ • Frameworks Build on the DOM

React, Vue, and Angular automate DOM updates, but under the hood they still rely on the same reading and writing operations you will learn in this lesson.

The core question this lesson addresses is straightforward: once an HTML page is loaded and displayed, how does JavaScript read existing content and then change it in real time? Specifically, how do you work with an element's text, its HTML attributes (like src or href), and its CSS classes?

SECTION 2

Core Principles & Definitions

Before you start writing code, you need a mental model for how the browser organizes a web page. Every HTML document is parsed into a tree of objects called nodes. Each HTML tag becomes an element node, the text inside a tag becomes a text node, and the tag's attributes (like id or class) are stored as properties on that element object. JavaScript can both read and write these properties, which instantly updates what the user sees on screen.

1

Text Content

Use textContent to read or set the plain text inside an element. Use innerHTML when you need to include HTML tags in the content.
2

Attributes

Use getAttribute() and setAttribute() to read or change any HTML attribute, such as src, href, or data-* custom attributes.
3

CSS Classes

Every element has a classList object with methods like add(), remove(), toggle(), and contains() for managing CSS classes cleanly.
4

Selection First

Before reading or changing anything, you must select the element. The two most common selectors are document.querySelector() (returns one element) and document.querySelectorAll() (returns a list).
✦

KEY TAKEAWAY

Think of the DOM as a live bulletin board. HTML is the original poster you pinned up. JavaScript is your hand reaching in to swap a word, change a photo, or add a colored sticky note — all while everyone is still reading the board. The three things you will change most often are the text (the words), the attributes (the details like image sources and links), and the classes (the styling labels).

SECTION 3

Visual Explanation — The DOM Tree

The diagram below shows how a small HTML snippet is represented as a DOM tree. Each rounded box is a node. Element nodes are colored, and the text inside them appears as child text nodes. Notice how attributes like id, class, and href are attached to their parent element nodes. JavaScript accesses any of these through the DOM API.

<div id="app"><h1 class="title"><a href="/about">"Hello World""About Us"JavaScript — Readconst h1 = document .querySelector('h1');h1.textContent// "Hello World"h1.getAttribute('class')// "title"h1.classList.contains('title')// trueJavaScript — Updateconst link = document .querySelector('a');link.textContent = 'Info';link.setAttribute( 'href', '/info');link.classList.add('btn');
The tree at the top represents a simple HTML document. The purple node is the root <div>. The cyan node is an <h1> and the pink node is an <a> link. The lower boxes show how JavaScript reads values on the left and updates them on the right.

Notice the pattern: you always start by selecting an element, then you either read a property to get its current value, or you assign a new value to change what the user sees. This select → read/write pattern is the foundation of every DOM interaction you will ever write.

SECTION 4

How It Works — The API in Detail

Reading & Writing Text

Every element node has two properties for its text. The textContent property returns or sets the plain text inside the element, stripping away any nested HTML tags. In contrast, innerHTML returns or sets the full HTML markup inside the element, which means it can include tags like <strong> or <em>. Use textContent when you only need plain text — it is faster and safer because it does not parse HTML.

TEXT CONTENT — READ & WRITE

element.textContent→returnsplaintextelement.textContent=′new′→setsplaintextelement.textContent → returns plain text element.textContent = 'new' → sets plain text

element is a reference to a DOM node obtained via querySelector() or similar. Reading returns a string; assigning replaces all child content.

INNER HTML — READ & WRITE

element.innerHTML→returnsHTMLstringelement.innerHTML=′<b>Hi</b>′→setsHTMLelement.innerHTML → returns HTML string element.innerHTML = '<b>Hi</b>' → sets HTML

⚠ Be cautious: setting innerHTML with user-supplied data can introduce security vulnerabilities (cross-site scripting). Prefer textContent unless you truly need HTML.

Reading & Writing Attributes

HTML attributes like src, href, alt, and custom data-* attributes are accessed through three main approaches. You can use getAttribute(name) and setAttribute(name, value) for any attribute. Many common attributes also have direct property access — for example, img.src or a.href. You can remove an attribute entirely with removeAttribute(name).

ATTRIBUTE METHODS

element.getAttribute(′href′)→′/about′element.setAttribute(′href′,′/home′)→changeslinkelement.removeAttribute(′disabled′)→removesattributeelement.getAttribute('href') → '/about' element.setAttribute('href', '/home') → changes link element.removeAttribute('disabled') → removes attribute

getAttribute() always returns a string (or null if the attribute does not exist). setAttribute() always takes two string arguments: the name and the new value.

Reading & Writing Classes

CSS classes control how elements look, and JavaScript gives you the classList object to manage them cleanly. Instead of clumsily rewriting the entire class attribute string, you can call individual methods on classList. The four methods you will use most often are add(), remove(), toggle(), and contains().

CLASSLIST METHODS

element.classList.add(′active′)→addsclasselement.classList.remove(′hidden′)→removesclasselement.classList.toggle(′dark′)→addsifabsent,removesifpresentelement.classList.contains(′active′)→true/falseelement.classList.add('active') → adds class element.classList.remove('hidden') → removes class element.classList.toggle('dark') → adds if absent, removes if present element.classList.contains('active') → true / false

You can pass multiple class names to add() and remove() at once, for example: el.classList.add('btn', 'primary').

SECTION 5

Detailed Breakdown — Choosing the Right Tool

With multiple ways to read and change elements, it helps to see them side by side. The table below organizes every method by what it does (read or write) and what part of the element it targets (text, attributes, or classes). The diagram that follows visualizes the decision-making process.

TaskProperty / MethodReturnsNotes
Read plain textel.textContentStringStrips HTML tags; fastest option
Write plain textel.textContent = '...'—Replaces all children with text
Read HTMLel.innerHTMLString (HTML)Includes nested tags
Write HTMLel.innerHTML = '...'—Parses HTML; avoid with user input
Read attributeel.getAttribute('name')String or nullWorks for any attribute
Write attributeel.setAttribute('name', 'val')—Creates the attribute if missing
Remove attributeel.removeAttribute('name')—Removes it from the element entirely
Add classel.classList.add('x')—No effect if class already present
Remove classel.classList.remove('x')—No error if class is absent
Toggle classel.classList.toggle('x')BooleanReturns true if class was added
Check classel.classList.contains('x')BooleanUseful in conditionals

Complete reference of DOM read/write methods for text, attributes, and classes

Decision Flowchart: Which Method Should I Use?What do you want to change?TextAttributeClassNeed HTML tags?Which attribute?What operation?YesNoinnerHTMLtextContentStandarddata-*el.srcdataset.xAdd/RemoveToggle/Check.add().toggle()Quick Bonus — getAttribute() vs. Direct Propertyimg.getAttribute('src') → "photo.jpg" (exact HTML value)img.src → "https://site.com/photo.jpg" (full URL)Direct properties sometimes return resolved/computed values, not the raw attribute string.
This flowchart helps you decide which DOM method to use based on whether you need to modify text, attributes, or classes. The bonus box at the bottom highlights a subtle difference between getAttribute() and direct property access.

SECTION 6

Worked Example — Building a Profile Card Editor

Imagine you have a simple profile card on a web page. The HTML looks like this:

STARTING HTML

<divid="card"class="cardlight"><imgsrc="old−avatar.png"alt="Userphoto"><h2>JaneDoe</h2><pclass="bio">WebdeveloperfromTexas.</p></div><div id="card" class="card light"> <img src="old-avatar.png" alt="User photo"> <h2>Jane Doe</h2> <p class="bio">Web developer from Texas.</p> </div>

The card has classes card and light. The image points to old-avatar.png. We want to update the name, bio, image, and toggle dark mode.

Profile Card Editor — Step by Step

  1. 1

    Step 1 — Select the Elements

    We use querySelector() to grab references to each element we need to modify. We store them in variables so we can reuse them. const card = document.querySelector('#card'); const img = card.querySelector('img'); const name = card.querySelector('h2'); const bio = card.querySelector('.bio');
    We now hold four element references: card, img, name, bio
  2. 2

    Step 2 — Update the Name (Text)

    To change the name displayed on the card, we set textContent on the <h2> element. name.textContent = 'Alex Rivera';
    The heading instantly changes from "Jane Doe" to "Alex Rivera" on screen.
  3. 3

    Step 3 — Update the Bio (HTML Content)

    This time we want bold text inside the bio, so we use innerHTML instead of textContent. bio.innerHTML = '<strong>Full-stack</strong> engineer from Austin.';
    The bio now reads Full-stack engineer from Austin — with "Full-stack" displayed in bold.
  4. 4

    Step 4 — Update the Image (Attribute)

    We change the src and alt attributes on the <img> element. img.setAttribute('src', 'new-avatar.png'); img.setAttribute('alt', 'Photo of Alex Rivera');
    The browser immediately fetches and displays new-avatar.png with updated alt text.
  5. 5

    Step 5 — Toggle Dark Mode (Class)

    We remove the light class and add a dark class to the card. Alternatively, we can toggle them. card.classList.remove('light'); card.classList.add('dark');
    The card's class attribute becomes "card dark". If the CSS defines different colors for .dark, the card's appearance changes instantly.
  6. 6

    Step 6 — Verify the Changes

    We can read back the values to confirm our updates took effect. console.log(name.textContent); // 'Alex Rivera' console.log(img.getAttribute('src')); // 'new-avatar.png' console.log(card.classList.contains('dark')); // true
    All three reads confirm the DOM now holds the updated values.

SECTION 7

Strengths, Pitfalls, and Best Practices

Now that you know the methods, let's discuss when each shines and where things can go wrong. Every tool in the DOM API has trade-offs, and picking the right one will save you from subtle bugs and security issues.

ApproachStrengthsPitfalls
textContentFast, safe from XSS attacks, simple to use for plain text updates.Cannot insert HTML — all tags are treated as literal text characters.
innerHTMLLets you inject formatted HTML, create new child elements on the fly.Vulnerable to cross-site scripting (XSS) if you insert user-supplied strings. Slower because the browser re-parses HTML.
getAttribute / setAttributeWorks for any attribute, including custom data-* attributes. Universal and predictable.Returns the raw HTML string, which may differ from the resolved property (e.g., relative vs. absolute URLs).
Direct property access (el.src)Shorter syntax, returns the computed/resolved value (full URL, boolean for disabled, etc.).Not available for all attributes — custom or non-standard attributes require getAttribute.
classListClean API with add, remove, toggle, contains. Does not accidentally overwrite other classes.Only manages class names — does not directly set inline styles. Use el.style for that.
className (the older way)Simple string replacement; sometimes useful when you want to set all classes at once.Replaces the entire class list — easy to accidentally remove classes you wanted to keep.

Strengths and pitfalls of common DOM reading/writing approaches

🛡️

BEST PRACTICE

Default to textContent for text and classList for classes. Only reach for innerHTML when you genuinely need HTML, and never put unsanitized user input into it. Think of it like a microwave versus an oven: the microwave (textContent) is quick and safe for everyday tasks, while the oven (innerHTML) is powerful but you need to be careful not to burn yourself.

SECTION 8

Connection to Frameworks & Advanced Concepts

The methods you have learned are sometimes called "vanilla DOM" manipulation — meaning they use the browser's built-in API with no extra libraries. Modern frameworks like React, Vue, and Angular abstract these operations behind higher-level patterns, but they still perform the same underlying reads and writes. Understanding vanilla DOM makes you a stronger developer because you can debug framework behavior, build lightweight features without adding dependencies, and perform well in technical interviews.

ConceptVanilla DOM (This Lesson)React / Vue Equivalent
Updating textel.textContent = 'Hello'Change state variable → framework re-renders automatically
Updating attributesel.setAttribute('src', url)Bind attribute to state: <img :src="url"> (Vue)
Toggling classesel.classList.toggle('active')Conditional class: className={active ? 'active' : ''} (React)
Selectiondocument.querySelector()useRef() hook (React) or $refs (Vue)
Batch updatesManual — each change triggers a repaintVirtual DOM diffs changes and batches DOM writes for efficiency

How vanilla DOM operations map to modern framework patterns

As you advance, you will encounter concepts like the Virtual DOM (a lightweight copy of the real DOM that frameworks use to minimize expensive updates), data binding (automatically syncing a variable's value with what appears on screen), and Shadow DOM (an encapsulated DOM subtree used in Web Components). All of these build on the same reading and writing primitives you practiced today.

💡

Looking Ahead

Once you are comfortable reading and updating existing elements, the next step is creating and removing elements using document.createElement(), appendChild(), and remove(). Then you will learn about event listeners so that your updates happen in response to user actions like clicks and keystrokes.

SECTION 9

Practice Problems

Test your understanding with these five problems. They start conceptual and build toward more challenging scenarios. For each one, think through your answer before checking.

Question 1: CONCEPTUAL

Explain the difference between textContent and innerHTML. When would you choose one over the other?
Show answer
textContent reads or sets only the plain text inside an element — any HTML tags are treated as literal characters, not markup. innerHTML reads or sets the full HTML string, so tags like <strong> are parsed and rendered. You should use textContent when you only need plain text because it is faster and protects against cross-site scripting (XSS) attacks. Use innerHTML only when you intentionally need to insert HTML structure, and never with unsanitized user input.

Question 2: BASIC

Given this HTML: <p id="msg">Welcome!</p>, write JavaScript to (a) read the current text, (b) change it to "Goodbye!", and (c) add a class called highlight.
Show answer
First, select the element: const p = document.querySelector('#msg'); (a) Read the text: console.log(p.textContent); // 'Welcome!' (b) Change it: p.textContent = 'Goodbye!'; (c) Add a class: p.classList.add('highlight'); Each operation follows the select → act pattern. The classList.add() call appends the new class without removing any existing ones.

Question 3: INTERMEDIATE

You have an <img id="photo" src="cat.jpg" alt="A cat"> tag. Write code that checks whether the alt attribute contains the word "cat". If it does, change the src to "dog.jpg" and the alt to "A dog".
Show answer
Select the image: const photo = document.querySelector('#photo'); Read the alt attribute: const altText = photo.getAttribute('alt'); Check if it includes 'cat': if (altText.includes('cat')) { photo.setAttribute('src', 'dog.jpg'); photo.setAttribute('alt', 'A dog'); } The includes() method is a standard string method that returns true if the substring is found. We use setAttribute() for both the src and alt because both are HTML attributes on the image element.

Question 4: APPLIED

You are building a to-do list. Each item looks like <li class="task">Buy groceries</li>. When a user completes a task, you want to add a done class and prepend the text with a ✓ symbol. Write a function completeTask(li) that takes a list item element and makes both changes, but only if the task has not already been marked done.
Show answer
Here is the function: function completeTask(li) { if (!li.classList.contains('done')) { li.classList.add('done'); li.textContent = '✓ ' + li.textContent; } } The classList.contains('done') check prevents double-marking. We read the existing textContent, prepend the checkmark, and write it back. This combines reading (contains, textContent) and writing (add, textContent assignment) in a single real-world workflow.

Question 5: CRITICAL THINKING

A classmate writes this code to toggle dark mode: document.body.className = 'dark'; It works — the page goes dark. But later they discover that their navigation menu has lost its styling. Explain what went wrong and write a corrected version of the code.
Show answer
The problem is that className replaces the entire class string. If the body originally had classes like "app loaded nav-open", setting className = 'dark' wipes them all and replaces them with just "dark". The nav menu relied on one of those removed classes for its styling. The fix is to use classList.toggle('dark') instead: document.body.classList.toggle('dark'); This adds or removes only the dark class while leaving all other classes untouched. This is precisely why classList is preferred over className for toggling individual classes.

SUMMARY

Lesson Summary

Summary

In this lesson you learned how JavaScript interacts with the Document Object Model (DOM) to read and update web page content in real time. You explored three core areas: text (using textContent for plain text and innerHTML for HTML markup), attributes (using getAttribute(), setAttribute(), and direct property access), and CSS classes (using classList.add(), remove(), toggle(), and contains()).

The universal pattern is select → read or write: use querySelector() to grab an element, then access its properties or call its methods. Remember to prefer textContent over innerHTML for safety, and always use classList instead of overwriting className to avoid accidentally removing other classes. These vanilla DOM skills are the foundation of every interactive web application — including those built with frameworks like React and Vue.

Varsity Tutors • JavaScript • Reading & Updating DOM — Read and update text, attributes, and classes