JavaScript textContent, innerText, innerHTML and style Explained
Change what an element shows with textContent, innerText and innerHTML, and how it looks with style and cssText. Learn the differences and avoid XSS.
What you will learn
You can already find elements. Now you will change them. In this lesson you will learn how to change an element’s text and HTML with textContent, innerText and innerHTML, and how to change its look with style and style.cssText. You will also learn the most important safety rule of DOM scripting: never put user input into innerHTML.
Two things you can change
Every visible element has two parts you may want to change:
| Part | Tools in this lesson |
|---|---|
| Content (what it says) | textContent, innerText, innerHTML |
| Style (how it looks) | style, style.cssText, getComputedStyle |
All examples use this small page, also loaded in Practice in Editor:
<h1 id="title">Content Practice</h1>
<div id="card" class="card">
Hello <b>World</b>
<span id="secret" style="display: none">(secret text)</span>
</div>
<p id="message">Welcome!</p>
<ul id="list"></ul>
<div id="bar" class="bar"></div>
.card {
border: 1px solid #ccc;
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
}
.bar {
height: 14px;
width: 0;
background: #2563eb;
border-radius: 7px;
}
Start with these variables:
const card = document.getElementById("card");
const message = document.getElementById("message");
Changing content
There are three properties for content. They look similar, but they behave very differently.
textContent: plain text
textContent reads or writes the text of an element and everything inside it. The browser treats what you write as plain text, never as HTML.
console.log(card.textContent.trim()); // "Hello World (secret text)"
message.textContent = "Your order is ready!";
Two important facts:
- It returns text from all elements inside, including hidden ones. That is why
(secret text)appears above even though it isdisplay: none. - When you set it, all the old children are replaced by a single text node. Any tags in your text are shown as letters, not as tags:
message.textContent = "Order <b>confirmed</b>!";
// The page shows: Order <b>confirmed</b>! (the tags are visible as text)
innerText: the text a person can see
innerText is aware of CSS. It returns only the text that is visible on the screen, with line breaks the way the user sees them.
console.log(card.innerText); // "Hello World" (no hidden text)
It is useful when you want what the user actually reads, for example to copy it. Because it must check the layout, it is slower than textContent, and it behaves a little differently in different browsers. When you set it, "\n" becomes a <br>.
innerHTML: the HTML inside
innerHTML reads or writes the HTML markup inside an element. When you set it, the browser parses your string and builds real elements from it.
console.log(card.innerHTML.trim());
// Hello <b>World</b>
// <span id="secret" style="display: none">(secret text)</span>
message.innerHTML = "Order <b>confirmed</b>!";
// The page shows: Order confirmed! (with "confirmed" in bold)
Comparing the three
Assume <div id="box">Hi <b>there</b><span hidden>!</span></div>:
| Property | Reading gives | Setting "<i>X</i>" |
|---|---|---|
textContent |
"Hi there!" (all text, even hidden) |
Shows the letters <i>X</i> |
innerText |
"Hi there" (visible text only) |
Shows the letters <i>X</i> |
innerHTML |
'Hi <b>there</b><span hidden>!</span>' |
Shows X in italics |
Which one should I use?
- Showing text (names, messages, numbers)? Use
textContent. It is fast and safe. - Need real HTML from your own trusted code? Use
innerHTML. - Reading what the user sees? Use
innerText.
Tip: To change the text of an
<input>,<textarea>or<select>, you do not use these. Use the.valueproperty instead.
innerHTML in real work
Render a list from data
Most real pages show data that comes from an array or a server. A common pattern is to make an HTML string with map() and join():
const fruits = ["Apple", "Banana", "Mango"];
document.getElementById("list").innerHTML = fruits
.map(function (fruit) {
return "<li>" + fruit + "</li>";
})
.join("");
Template literals make HTML strings much easier to read:
const products = [
{ name: "Pen", price: 10 },
{ name: "Notebook", price: 60 },
{ name: "Bag", price: 800 }
];
document.getElementById("list").innerHTML = products
.map((p) => `<li><strong>${p.name}</strong>: ₹${p.price}</li>`)
.join("");
Always
join(""). Without it, an array becomes text with commas between the items.
Clear an element
document.getElementById("list").innerHTML = ""; // works
document.getElementById("list").textContent = ""; // also works, and is a little cleaner
The security rule: XSS
XSS (Cross-Site Scripting) is an attack where a user puts harmful code into your page. It usually happens when you put user input into innerHTML.
Imagine a comment box. A bad user types this as their “comment”:
<img src="x" onerror="stealUserData()">
If you write commentBox.innerHTML = userComment, the browser builds the image, the image fails to load, and the attacker’s code runs on every visitor’s browser. (A plain <script> tag inserted through innerHTML does not run, but tricks like onerror do.)
The fix is simple:
// Unsafe: the browser reads the text as HTML
commentBox.innerHTML = userComment;
// Safe: the browser shows the text exactly as typed
commentBox.textContent = userComment;
Here is a harmless version you can run. Type <b>bold</b> in the box and compare the two outputs:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>textContent vs innerHTML</title>
<style>
body { font-family: sans-serif; padding: 20px; }
input { width: 100%; padding: 8px; font-size: 16px; box-sizing: border-box; }
.out { margin-top: 12px; padding: 12px; border-radius: 8px; }
.safe { background: #ecfdf5; }
.unsafe { background: #fef2f2; }
</style>
</head>
<body>
<label for="text">Type something (try: <b>bold</b>)</label>
<input id="text" type="text">
<div class="out safe">Safe (textContent): <span id="safe"></span></div>
<div class="out unsafe">Unsafe (innerHTML): <span id="unsafe"></span></div>
<script>
const input = document.getElementById("text");
input.addEventListener("input", function () {
document.getElementById("safe").textContent = input.value;
document.getElementById("unsafe").innerHTML = input.value;
});
</script>
</body>
</html>
Rules to remember:
- Text from users, forms, URLs or an API → use
textContent. - If you must build HTML from such data, escape it first (turn
<into<and so on), or build the elements withcreateElement(a later lesson). innerHTMLis fine for strings you wrote yourself in your own code.
Another innerHTML trap: +=
list.innerHTML += "<li>Orange</li>"; // looks fine, but it is risky
This reads the whole HTML, adds a string, and then rebuilds every child from scratch. It throws away anything that was attached to the old children, such as event listeners or text the user typed in an input. If you only want to add to the end, use insertAdjacentHTML:
list.insertAdjacentHTML("beforeend", "<li>Orange</li>");
insertAdjacentHTML takes a position: "beforebegin", "afterbegin", "beforeend" or "afterend". It leaves the existing children alone. (The lesson on creating and removing elements shows an even better way.)
outerHTML
innerHTML is the content inside an element. outerHTML includes the element’s own tag too, and setting it replaces the element.
console.log(message.outerHTML); // <p id="message">Order <b>confirmed</b>!</p>
Changing styles
Every element has a style property. It gives you the element’s inline style, the same as writing style="..." in HTML. Each CSS property becomes a property of style.
const title = document.getElementById("title");
title.style.color = "tomato";
title.style.backgroundColor = "#fff7ed";
title.style.fontSize = "32px";
title.style.textAlign = "center";
Rules:
- CSS names with dashes become camelCase:
background-colorbecomesbackgroundColor,font-sizebecomesfontSize,border-radiusbecomesborderRadius. - Every value is a string, and numbers need a unit:
"32px","1.5rem","50%". - To remove an inline style, set it to an empty string:
title.style.color = "".
title.style.width = 200; // ignored: no unit
title.style.width = "200px"; // works
title.style.color = ""; // removes the inline colour
You can also use the dash style with square brackets: title.style["font-size"] = "32px".
Reading styles: inline vs computed
element.style only knows the inline styles. It does not know about your CSS file.
console.log(card.style.padding); // "" (the padding comes from the stylesheet)
console.log(getComputedStyle(card).padding); // "12px" (what the browser really uses)
console.log(getComputedStyle(card).color); // "rgb(0, 0, 0)"
Use getComputedStyle(element) to read the final style. It is read-only: to change a style, you use element.style. Computed values are returned in a standard form, so colours are rgb(...), and sizes are in pixels.
style.cssText: many styles at once
card.style.cssText = "background: #ecfdf5; border: 2px solid #10b981; padding: 20px;";
Be careful: cssText replaces all the inline styles that were there before. If you only want to add or change a few, use the individual properties. Use += to add to the end:
card.style.cssText += "border-radius: 16px;";
setProperty, getPropertyValue, removeProperty
These methods use the real CSS names (with dashes). They are the only way to work with CSS variables:
card.style.setProperty("--accent", "#10b981");
console.log(card.style.getPropertyValue("--accent")); // "#10b981"
card.style.setProperty("color", "red", "important"); // add !important
card.style.removeProperty("color");
Changing a CSS variable on the <html> element is a very powerful way to change a whole theme with one line:
document.documentElement.style.setProperty("--brand", "#7c3aed");
When not to use style
Inline styles are great for values that change a lot or come from data (a progress bar width, a colour picked by the user, a position). For simple on/off looks, such as “make this red when it has an error”, it is better to put the look in your CSS and just switch a class in JavaScript. That is the next lesson, and it keeps your styles in your CSS file, where they belong.
Use style for |
Use classes (classList) for |
|---|---|
A progress bar width, e.g. 65% |
Error, success, active, hidden states |
| A colour or size chosen by the user | Themes like dark mode |
| Animations that depend on numbers | Anything reusable in several places |
Real use cases
1. Live character counter
Twitter-style counters show how many characters are left, and turn red near the limit.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Character Counter</title>
<style>
body { font-family: sans-serif; padding: 20px; max-width: 420px; }
textarea { width: 100%; height: 90px; padding: 8px; font-size: 16px; box-sizing: border-box; }
#count { font-weight: bold; }
</style>
</head>
<body>
<label for="bio">Your bio (max 60 characters)</label>
<textarea id="bio"></textarea>
<p><span id="count">60</span> characters left</p>
<script>
const bio = document.getElementById("bio");
const count = document.getElementById("count");
const max = 60;
bio.addEventListener("input", function () {
const left = max - bio.value.length;
count.textContent = left;
count.style.color = left < 10 ? "crimson" : "green";
});
</script>
</body>
</html>
2. Progress bar
The width comes from a number, so style is the right tool. Move the slider to see it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Progress Bar</title>
<style>
body { font-family: sans-serif; padding: 20px; max-width: 420px; }
.track { background: #e5e7eb; border-radius: 8px; overflow: hidden; margin: 12px 0; }
.fill { height: 16px; width: 0; background: #2563eb; transition: width 0.3s; }
</style>
</head>
<body>
<label for="range">Course progress</label>
<input id="range" type="range" min="0" max="100" value="0">
<div class="track"><div class="fill" id="fill"></div></div>
<p id="label">0% complete</p>
<script>
const range = document.getElementById("range");
const fill = document.getElementById("fill");
const label = document.getElementById("label");
range.addEventListener("input", function () {
fill.style.width = range.value + "%";
label.textContent = range.value + "% complete";
});
</script>
</body>
</html>
3. Render product cards from data
This is how many online shops draw their product grids. The data is our own trusted array, so innerHTML is fine here. The inStock value changes the look.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Product Cards</title>
<style>
body { font-family: sans-serif; padding: 20px; }
#shop { display: flex; gap: 12px; flex-wrap: wrap; }
.product { width: 150px; padding: 12px; border: 1px solid #ccc; border-radius: 8px; }
.product h3 { margin: 0 0 6px; }
.price { font-weight: bold; margin: 0; }
</style>
</head>
<body>
<h2>Our Products</h2>
<div id="shop"></div>
<script>
const products = [
{ name: "Pen", price: 10, inStock: true },
{ name: "Notebook", price: 60, inStock: true },
{ name: "Bag", price: 800, inStock: false }
];
const shop = document.getElementById("shop");
shop.innerHTML = products
.map(function (p) {
return `
<div class="product" style="opacity: ${p.inStock ? 1 : 0.4}">
<h3>${p.name}</h3>
<p class="price">₹${p.price}</p>
<p>${p.inStock ? "In stock" : "Sold out"}</p>
</div>`;
})
.join("");
</script>
</body>
</html>
4. Show an error message
const emailError = document.getElementById("email-error");
function showError(text) {
emailError.textContent = text; // safe: plain text
emailError.style.color = "crimson";
}
showError("Please enter a valid email.");
Common mistakes
- Using
innerHTMLwith user input. This opens the door to XSS. UsetextContentfor any text you did not write yourself. - Expecting
textContentto render tags."<b>Hi</b>"shows the tags as letters. UseinnerHTMLfor trusted HTML. - Forgetting
join("")when building HTML from an array, which leaves commas between the items. - Using
innerHTML +=in a loop. It rebuilds all the children every time and removes event listeners. UseinsertAdjacentHTML, or build all the HTML first and set it once. - Using
textContenton an input. The text inside an input is not its content. Useinput.value. - Writing CSS names with dashes.
style.font-sizeis wrong (it becomesstyle.font - size). Usestyle.fontSize. - Forgetting units.
style.width = 200does nothing. Use"200px". - Reading a stylesheet value from
style.el.style.coloris empty if the colour comes from your CSS file. UsegetComputedStyle(el).color. - Using
cssText =and losing other inline styles. It replaces everything. - Using
innerTextfor performance-sensitive code. It forces the browser to calculate the layout.textContentis faster.
Practice
- Print
card.textContent,card.innerTextandcard.innerHTML. Explain why the three results are different. - Change the
#messagetext withtextContent. Then set"<b>Hi</b>"withtextContentand then withinnerHTML. What is the difference? - Use an array of 4 names and
map()+join("")to fill the#listwith<li>items. - Change the title’s colour, background colour and font size with the
styleproperty. - Print
card.style.paddingandgetComputedStyle(card).padding. Why do they differ? - Use
style.cssTexton the card to set three styles at once. Then trycssText +=and check what stays. - Set a CSS variable with
style.setProperty("--accent", "hotpink")and read it back withgetPropertyValue. - Make a character counter for a
<textarea>that turns red when fewer than 10 characters are left. - Set
#barto 25%, 50% and 100% width. Then connect it to a range slider. - Challenge: make a small “comment box” page. Whatever the user types should be added as a new
<li>in a list usinginsertAdjacentHTML, but written in a safe way (hint: first put the text in a<li>usingtextContenton a temporary element, and read it back withinnerHTML).
Recap
textContentreads and writes plain text, including hidden text. It is fast and safe, and it is the best default.innerTextreturns only the visible text, as the user sees it. It is slower because it depends on the layout.innerHTMLreads and writes HTML markup. Use it only with content you wrote yourself, because with user input it causes XSS.- Build lists from data with
map()andjoin(""). AvoidinnerHTML +=; useinsertAdjacentHTMLto add to the end. element.stylecontrols inline styles. Use camelCase names (backgroundColor) and string values with units ("20px").getComputedStyle(element)reads the final style, andstyle.cssTextsets many styles at once (replacing the old inline ones).setPropertyandgetPropertyValuework with CSS variables and!important.- Use
stylefor values that come from numbers or data. For on/off looks, switch classes instead. - Next you will learn
classList, the clean way to add, remove and toggle CSS classes.