Command Palette

Search for a command to run...

Hectal
PHASE 8Intermediate ~21 min· topic 3 of 8

Topic 8.3

Changing Content

In one line

textContent reads and writes plain text, innerText reads the text as it appears on screen, and innerHTML reads and writes HTML markup; use textContent for anything a user typed, because innerHTML turns text into real elements and opens the door to cross-site scripting (XSS).

Think of it like this

A notice board with two kinds of pins. With a label maker you print exactly the letters you typed: if you type "<b>SALE</b>", the label shows those angle brackets as letters. That is textContent. With a poster printer, the text you send is read as design instructions: "<b>SALE</b>" comes out as the word SALE in bold. That is innerHTML. The poster printer is powerful, but if a stranger slips their own instructions into your order, the printer obeys them too.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

textContent
The plain text of an element and everything inside it. Setting it puts in exactly that text, never HTML.
innerText
The text as the user sees it on screen, following CSS. Reading it can make the browser work out layout.
innerHTML
The contents of an element as an HTML string. Setting it parses the string and builds new elements.
outerHTML
Like innerHTML, but including the element's own tag.
Parse
Read a string and turn it into structure; here, turning HTML text into elements.
XSS
Cross-site scripting: an attack where someone gets their own code to run in your page, often through user text inserted as HTML.
Sanitizer
A tool that cleans HTML by removing anything that could run code, keeping only safe tags.
Escape
Turn special characters like < and & into a form that is shown as text instead of read as markup (&lt;, &amp;).

Step by step

01Read the three ways

Take <p>Hello <b>there</b><span hidden-by-css> (secret)</span></p>. textContent gives all the text, innerText gives what you see, innerHTML gives the markup.

app.jswhole filejavascript
p.textContent  // "Hello there (secret)"
p.innerText    // "Hello there"
p.innerHTML    // "Hello <b>there</b><span class=\"hidden\"> (secret)</span>"

02Write text with textContent

This is the everyday choice. Whatever string you give, the user sees exactly those characters.

app.jswhole filejavascript
const status = document.getElementById("status");
status.textContent = "Saved 3 items <safely>";
// the page shows: Saved 3 items <safely>

03Write markup you wrote yourself with innerHTML

When the HTML is a fixed string from your own code, innerHTML is fine and convenient. The rule is about where the data comes from, not about the property itself.

app.jswhole filejavascript
const empty = document.getElementById("empty");
empty.innerHTML = "<strong>No results.</strong> Try another word.";

04Mixing your markup with user data

Build the structure yourself and drop the user's text in with textContent. The user's text can then never become markup.

app.jswhole filejavascript
function showComment(author, text) {
  const li = document.createElement("li");
  const name = document.createElement("strong");
  name.textContent = author;          // user data: text only
  li.append(name, ": ", text);         // strings passed to append become text nodes
  document.getElementById("comments").append(li);
}

05Where XSS gets in, and where it's stopped

Follow the data. Anything from outside your code is untrusted. It only becomes dangerous at a sink that parses HTML, such as innerHTML, outerHTML, insertAdjacentHTML or document.write.

Where XSS gets in, and where it's stoppeddiagram
Rendering diagram…

Try it yourself

  1. 1

    Show hidden text

    In the first example, delete the .hidden rule from the CSS tab. Predict which log line changes, then run.

  2. 2

    Line breaks in innerText

    In the first example, add msg.innerText = "Line one\nLine two"; and then console.log(msg.innerHTML);. What did the \n become? Try the same with textContent.

  3. 3

    Test your own defence

    In the third example, change comment to "Tom & <i>Jerry</i>". Predict what each list shows. This "type some markup into the box" test is exactly how you check a real form for XSS.

Code & diagrams

textContent, innerText and innerHTML side by side New tab

`textContent` includes the hidden span; `innerText` follows the CSS and leaves it out. `innerHTML` shows the markup; `outerHTML` adds the `<p>` itself.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Hello there (secret)
Hello there
Hello <b>there</b><span class="hidden"> (secret)</span>
true
Setting text versus setting HTML New tab

The first span shows the angle brackets as characters: the browser escaped them (`&lt;` is how `<` is written as text in HTML). The second span parsed the string and created a real `<b>` element.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

&lt;b&gt;Hi&lt;/b&gt; &amp; welcome
0 1
<b>
User text must stay text (the XSS defence) New tab

The stranger's text became real `<img>` and `<u>` elements in the unsafe list. These two are harmless, but the same door lets in elements whose attributes run code. With `textContent` the stranger's markup is only ever shown as characters.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Elements created by the safe way: 0
Elements created by the unsafe way: 2
<img>

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Grow a list with innerHTML +=

Add a click listener to the first <li> of a list, then run list.innerHTML += "<li>New</li>"; and click the first item.

terminal
$ the page shows
── what you'll see ──
Clicking the first item does nothing any more, and any text typed into inputs inside the list has disappeared.

Break #2

Put a user's name into innerHTML

Write greeting.innerHTML = "Hello, " + nameFromForm; and type <i>Asha</i> into the name box.

terminal
$ the page shows
── what you'll see ──
Hello, Asha (in italics). The typed tags became a real <i> element instead of being shown.

Myth vs fact

Myth

innerHTML is safe because inserted <script> tags don't run.

Fact

It's true that <script> elements inserted by innerHTML don't execute, but other markup, such as event-handler attributes on elements, does run code. Never put untrusted text into innerHTML without a sanitizer.

Myth

innerText and textContent are the same.

Fact

textContent returns all text from the DOM, fast and layout-free. innerText returns the rendered text: it skips hidden elements, applies text-transform and line breaks, and can force a layout calculation. Their setters also differ in how they treat line breaks.

Myth

Escaping data once on the server makes innerHTML safe forever.

Fact

Safety depends on the context the data ends up in (HTML text, an attribute, a URL, a script). The robust habit in the browser is to put data in with textContent and attributes with setAttribute, so no escaping mistakes are possible.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Setting textContent to "" is a fast way to empty an element. element.replaceChildren() (no arguments) does the same and reads more clearly; with arguments it empties and inserts in one step.

  • ▸

    textContent on document or a doctype returns null (not ""), because the spec defines it only for elements, text, comments and fragments. document.documentElement.textContent gives the page's text.

  • ▸

    Trusted Types is enforced with the CSP header require-trusted-types-for 'script'. Once on, assigning a plain string to innerHTML throws, and only values created by a registered policy (usually wrapping a sanitizer) are allowed. It turns every forgotten innerHTML into a loud error instead of a silent hole. Chromium has supported it for years; check current support in other engines.

  • ▸

    Parsing HTML with innerHTML is fast for building large static chunks, often faster than thousands of createElement calls. Frameworks use <template> cloning for the same reason. Speed is never a reason to skip sanitizing untrusted input, though.

Remember this

  1. 1

    element.textContent gives all the text inside an element and its descendants, joined together, with the tags removed. It includes text that CSS hides. Setting it removes every child of the element and puts in one text node with exactly your string. Characters like < and & stay as visible characters; nothing is parsed as HTML. It is fast, safe and the right default for changing text.

  2. 2

    element.innerText gives the text as rendered: it skips elements hidden with display: none, follows CSS such as text-transform: uppercase, and turns <br> and block elements into line breaks. To do that it must know the current styles and layout, so reading it can force the browser to calculate layout (expensive, see Topic 8.8). Setting it is similar to textContent, except that line breaks in your string become <br> elements. Use it when you need what the user sees; otherwise prefer textContent.

  3. 3

    element.innerHTML reads the element's contents as an HTML string. Setting it makes the browser parse your string as HTML, remove the old children and build new elements from the markup. element.outerHTML is the same but includes the element itself, and setting it replaces the whole element. insertAdjacentHTML(position, html) parses markup and inserts it without destroying what's already there (Topic 8.6).

  4. 4

    Cross-site scripting (XSS) is the danger. If text that came from a user (a comment, a name, a search box, a URL parameter) is put into innerHTML, the browser treats it as markup. Anyone can then add their own elements to your page, including ones with event-handler attributes that run JavaScript. That code runs with your site's permissions: it can read what the user sees, act as them and send their data elsewhere. <script> tags inserted with innerHTML do not run, but that does not make it safe, because other markup can still run code.

  5. 5

    The defence is simple: treat user data as text. Use textContent (or createElement + textContent, Topic 8.6) for anything you didn't write yourself. If you truly need user-supplied HTML (a rich-text editor), pass it through a well-tested sanitizer such as DOMPurify, which removes anything that can run code, and add a Content Security Policy header that blocks inline scripts. Browsers are also adding a built-in Sanitizer API (element.setHTML()) and Trusted Types (which make innerHTML refuse plain strings); check support before relying on them. Verify your defence by entering text like <b>test</b> in every input: it should appear with its angle brackets visible.

  6. 6

    A performance trap: el.innerHTML += "<li>New</li>" looks like it adds one item, but it actually reads all the existing HTML as a string, adds yours, then throws away every child element and rebuilds them all. Event listeners on the old elements are lost, typed text in inputs is lost, and it gets slower as the list grows. To add content, use append or insertAdjacentHTML instead.

Explain it without notes

01

What are the differences between textContent, innerText and innerHTML?

02

What is XSS, and how does innerHTML make it possible?

03

Why is element.innerHTML += "..." a bad way to add content?

04

How would you safely display a comment that a user wrote, including their name in bold?

Practice

01

A paragraph contains Price: <b>₹499</b>. Log its text without tags, then change only the number to ₹449 without using innerHTML.

02

Write a function showMessage(text) that puts any text into #message safely and returns the number of child elements #message has afterwards. Call it with "<em>hi</em>".

Trade-offs

  • ↔

    innerHTML with your own fixed markup is short and fast to write; building with createElement is longer but can't be tricked by data, keeps references to the new elements and never destroys existing ones.

  • ↔

    A sanitizer lets users format their content but adds a dependency and must be kept up to date; for most fields (names, comments, search terms), plain text is all you need.

Done when you can

  • I use textContent to read and write plain text.

  • I know when innerText differs from textContent, and that reading it can force layout.

  • I can explain how innerHTML with user input leads to XSS, and how to prevent and test for it.

  • I never grow content with innerHTML +=.

  • I can combine my own structure with user text using createElement, append and textContent.