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 (<,&).
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.
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.
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.
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.
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.
Try it yourself
- 1
Show hidden text
In the first example, delete the
.hiddenrule from the CSS tab. Predict which log line changes, then run. - 2
Line breaks in innerText
In the first example, add
msg.innerText = "Line one\nLine two";and thenconsole.log(msg.innerHTML);. What did the\nbecome? Try the same withtextContent. - 3
Test your own defence
In the third example, change
commentto"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` includes the hidden span; `innerText` follows the CSS and leaves it out. `innerHTML` shows the markup; `outerHTML` adds the `<p>` itself.
Preview
Console
Nothing logged yet.Expected console output
Hello there (secret)
Hello there
Hello <b>there</b><span class="hidden"> (secret)</span>
trueThe first span shows the angle brackets as characters: the browser escaped them (`<` is how `<` is written as text in HTML). The second span parsed the string and created a real `<b>` element.
Preview
Console
Nothing logged yet.Expected console output
<b>Hi</b> & welcome
0 1
<b>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
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.
Break #2
Put a user's name into innerHTML
Write greeting.innerHTML = "Hello, " + nameFromForm; and type <i>Asha</i> into the name box.
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
textContentto""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. - ▸
textContentondocumentor a doctype returnsnull(not""), because the spec defines it only for elements, text, comments and fragments.document.documentElement.textContentgives the page's text. - ▸
Trusted Types is enforced with the CSP header
require-trusted-types-for 'script'. Once on, assigning a plain string toinnerHTMLthrows, and only values created by a registered policy (usually wrapping a sanitizer) are allowed. It turns every forgotteninnerHTMLinto a loud error instead of a silent hole. Chromium has supported it for years; check current support in other engines. - ▸
Parsing HTML with
innerHTMLis fast for building large static chunks, often faster than thousands ofcreateElementcalls. Frameworks use<template>cloning for the same reason. Speed is never a reason to skip sanitizing untrusted input, though.
Remember this
- 1
element.textContentgives 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
element.innerTextgives the text as rendered: it skips elements hidden withdisplay: none, follows CSS such astext-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 totextContent, except that line breaks in your string become<br>elements. Use it when you need what the user sees; otherwise prefertextContent. - 3
element.innerHTMLreads 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.outerHTMLis 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
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 withinnerHTMLdo not run, but that does not make it safe, because other markup can still run code. - 5
The defence is simple: treat user data as text. Use
textContent(orcreateElement+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 makeinnerHTMLrefuse 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
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, useappendorinsertAdjacentHTMLinstead.
Explain it without notes
What are the differences between textContent, innerText and innerHTML?
What is XSS, and how does innerHTML make it possible?
Why is element.innerHTML += "..." a bad way to add content?
How would you safely display a comment that a user wrote, including their name in bold?
Practice
A paragraph contains Price: <b>₹499</b>. Log its text without tags, then change only the number to ₹449 without using innerHTML.
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
- ↔
innerHTMLwith your own fixed markup is short and fast to write; building withcreateElementis 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
textContentto read and write plain text.I know when
innerTextdiffers fromtextContent, and that reading it can force layout.I can explain how
innerHTMLwith 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,appendandtextContent.