JavaScriptBeginner

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.

All JavaScript lessons

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 is display: 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 .value property 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: &lt;b&gt;bold&lt;/b&gt;)</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 &lt; and so on), or build the elements with createElement (a later lesson).
  • innerHTML is 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-color becomes backgroundColor, font-size becomes fontSize, border-radius becomes borderRadius.
  • 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 innerHTML with user input. This opens the door to XSS. Use textContent for any text you did not write yourself.
  • Expecting textContent to render tags. "<b>Hi</b>" shows the tags as letters. Use innerHTML for 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. Use insertAdjacentHTML, or build all the HTML first and set it once.
  • Using textContent on an input. The text inside an input is not its content. Use input.value.
  • Writing CSS names with dashes. style.font-size is wrong (it becomes style.font - size). Use style.fontSize.
  • Forgetting units. style.width = 200 does nothing. Use "200px".
  • Reading a stylesheet value from style. el.style.color is empty if the colour comes from your CSS file. Use getComputedStyle(el).color.
  • Using cssText = and losing other inline styles. It replaces everything.
  • Using innerText for performance-sensitive code. It forces the browser to calculate the layout. textContent is faster.

Practice

  1. Print card.textContent, card.innerText and card.innerHTML. Explain why the three results are different.
  2. Change the #message text with textContent. Then set "<b>Hi</b>" with textContent and then with innerHTML. What is the difference?
  3. Use an array of 4 names and map() + join("") to fill the #list with <li> items.
  4. Change the title’s colour, background colour and font size with the style property.
  5. Print card.style.padding and getComputedStyle(card).padding. Why do they differ?
  6. Use style.cssText on the card to set three styles at once. Then try cssText += and check what stays.
  7. Set a CSS variable with style.setProperty("--accent", "hotpink") and read it back with getPropertyValue.
  8. Make a character counter for a <textarea> that turns red when fewer than 10 characters are left.
  9. Set #bar to 25%, 50% and 100% width. Then connect it to a range slider.
  10. Challenge: make a small “comment box” page. Whatever the user types should be added as a new <li> in a list using insertAdjacentHTML, but written in a safe way (hint: first put the text in a <li> using textContent on a temporary element, and read it back with innerHTML).

Recap

  • textContent reads and writes plain text, including hidden text. It is fast and safe, and it is the best default.
  • innerText returns only the visible text, as the user sees it. It is slower because it depends on the layout.
  • innerHTML reads 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() and join(""). Avoid innerHTML +=; use insertAdjacentHTML to add to the end.
  • element.style controls inline styles. Use camelCase names (backgroundColor) and string values with units ("20px").
  • getComputedStyle(element) reads the final style, and style.cssText sets many styles at once (replacing the old inline ones).
  • setProperty and getPropertyValue work with CSS variables and !important.
  • Use style for 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.