JS DOM: innerHTML, textContent, innerText, nodeValue
innerHTML
innerHTML is meant to be HTML code.
// change content of first p tag document.getElementsByTagName("p")[0].innerHTML = "<b>water</b>"; // the word water is shown as bold
textContent
// change content of first p tag document.getElementsByTagName("p")[0].textContent = "<b>water</b>";
when you set
document.getElementsByTagName("p")[0].textContent = "<b>water</b>";
the result is not bold. but the html b tag means it should be.
How to explain this conflict?
answer
(ai answer)
There is no conflict. <b> only makes text bold when the browser parses that string as HTML. textContent never does that.
What textContent actually does
textContent is defined as the plain-text content of a node. Assigning to it:
- Removes all current child nodes.
- Creates a single text node whose data is the string you gave it, character for character.
- Does not run the HTML parser.
So this:
document.getElementsByTagName("p")[0].textContent = "<b>water</b>";
puts the literal characters <, b, >, w, a, t, e, r, <, /, b, > into the page. The browser displays them as text, not as a <b> element. That is the specified, intended behavior.
Contrast with innerHTML
innerHTML does feed the string to the HTML parser, which builds real element nodes:
document.getElementsByTagName("p")[0].innerHTML = "<b>water</b>";
Now the paragraph contains a <b> element whose text is “water”, so it renders bold. That is why the two properties exist: one is for text, one is for markup.
Safer / more explicit alternatives
If you only want the word “water” bold and you control the markup, create the element yourself:
const p = document.getElementsByTagName("p")[0]; p.textContent = ""; // clear const b = document.createElement("b"); b.textContent = "water"; // still plain text p.appendChild(b);
Using textContent (or createTextNode) for user-supplied strings is also the usual recommendation against XSS, because those APIs never interpret < as the start of a tag.
but still, it creates a inconsistency because now, apparantly, part of the html can be in a state of not parsed. does this happen in other circumstances?
Yes. That state is normal. The inconsistency only appears if you treat the live page as one HTML string that must stay fully parsed. After load, the document is a tree. Markup is parsed only at specific moments. Everything else can hold characters that look like tags without those characters ever becoming elements.
The document is a tree, not a source file
The HTML parser runs when:
- the original document is loaded
- you assign
innerHTML/outerHTML - you call
insertAdjacentHTML,DOMParser,document.write, etc.
Between those moments, a text node is just a string of Unicode. <b>water</b> inside a text node is the same kind of data as hello or 2 < 3. The browser does not scan existing text nodes looking for tags.
“View Source” shows the original bytes. DevTools Inspector shows the live tree. After your textContent assignment, Inspect shows a single text node whose data is the characters <b>water</b>. That is the real document.
This happens all the time
1. Any API that writes text, not markup
textContent, innerText, nodeValue, createTextNode(), document.createTextNode(), form values copied into the page, textContent of user comments, etc. All of these can contain <, >, &.
2. Elements the parser itself treats as raw / RCDATA text
While parsing source HTML, these do not interpret nested tags as elements (with some end-tag exceptions):
script,style— raw texttextarea,title— RCDATA (entities decode, tags do not)- historically
xmp,iframebody in some modes,noscriptwhen scripting is on, obsoleteplaintext
So this is valid HTML and the <b> never becomes bold:
<textarea><b>water</b></textarea> <title>Use <b> for bold</title> <script>const s = "<b>water</b>";</script>
3. Comments and similar
<!-- <b>water</b> --> is a comment node. The string is not parsed as elements.
4. Attributes
data-note="<b>water</b>" stores that string on the attribute. It is not a <b> element. Serialization now typically escapes < and > in attributes so a later parse does not invent tags.
5. Foreign content, templates, inert trees
SVG/MathML have different rules. <template> holds an inert fragment until you clone it. JSON in <script type="application/json"> is text.
Why it does not “break HTML”
If you read innerHTML after the textContent assignment, the serializer escapes the text node so the string round-trips as text, not as a tag:
<b>water</b>
Parse that again and you still get a text node, not a bold element. The tree and the serialized markup stay consistent; they just encode “this is text” differently from “this is an element.”
(There are special cases: serializing text inside script/style does not escape the same way, which is why mutation-XSS bugs exist around those elements.)
Mental model
| Layer | Role of <b>water</b> |
|---|---|
HTML source / innerHTML setter |
Markup: parser may create a <b> element |
Text node / textContent |
Data: those eight characters, including < and > |
innerHTML getter |
Serialization: usually <b>water</b> so a later parse keeps it as text |
So the page is not “partly unparsed HTML.” It is a DOM in which some nodes are elements and some nodes are text. Text that resembles tags is an everyday case, not a special broken state. Parsing is an operation you invoke, not a permanent property of every substring in the tree.
textContent vs innerText
What is the difference between textContent and innerText
(ai answer)
textContent and innerText both get/set the text of a DOM node (ignoring HTML tags), but they differ significantly in what they include, how they handle styling/visibility, performance, and scope.
Key Differences
| Aspect | textContent (Node) |
innerText (HTMLElement) |
|---|---|---|
| What it returns | All text content of the node and its descendants, including hidden text, <script>, <style>, comments ignored, raw whitespace/line breaks from the markup |
Approx. the rendered/visible text a user would see (or get by selecting + copying). Ignores hidden elements, normalizes whitespace, respects <br>, and applies some CSS text transformations |
| CSS / visibility | Completely ignores CSS and layout. Includes display: none, visibility: hidden, etc. |
Aware of CSS styling and computed styles. Skips non-rendered/"human-readable" content |
| Performance | Fast — no layout/reflow needed | Slower — reading it can trigger a reflow to get up-to-date styles |
| Availability | All Node types |
Only HTMLElements |
| Setting value | Replaces all children with a single text node | Same, but converts line breaks into <br> elements |
| Standardization | Long-standing DOM standard | Originally non-standard (IE); later standardized |
Practical Example
<div id="demo"> <style>p { color: red; }</style> Visible text. <span style="display: none">Hidden text</span> <br> More text. </div>
element.textContent→ roughly"p { color: red; } Visible text. Hidden text More text."(includes style content + hidden text + original whitespace)element.innerText→ roughly"Visible text.\nMore text."(only visible rendered text, with line break from<br>)
When to Use Which
- Prefer
textContentin most cases: getting/setting raw text, better performance, no reflow, works on any node, safer for data extraction (no surprise CSS effects). It’s the modern default recommendation. - Use
innerTextwhen you specifically need the visible/rendered text as a user would experience it (e.g., accessibility checks, clipboard-like behavior, or UI testing that must ignore hidden content).
Note: Neither parses HTML (unlike innerHTML). Setting either one removes existing child nodes. Both are widely supported in modern browsers.
nodeValue
nodeValueis for setting content of text node only. [see get Node Name (Element Tag Name), Node Type, Node Value]- It is particularly useful when working with XML, such as SVG. [see Practical SVG Tutorial]
The most compatible and standard way of setting text content is first to get the element's content as a text node, then set nodeValue, like this:
node.firstChild.nodeValue = text
But, if a element's content is empty, it doesn't have any child (thus no text node), so you need to check if element has firstChild first.
const f_addTextNode = (xel, xtext) => { while (xel.firstChild) xel.removeChild(xel.firstChild); xel.appendChild(document.createTextNode(xtext)); }; f_addTextNode(document.getElementsByTagName("p")[0], "good");
History
nodeValueis in web standard since 1990s.innerHTMLis originally from Microsoft Internet Explorer. Is made into standard, supported by all browsers back to 2010.innerTextis originally from Microsoft Internet Explorer, but is now in html spec. It's not supported by Firefox as of 2015-03-13.textContentis in standard's spec around 2011, and is supported by browsers since about 2013.