innerHTML vs textContent
innerHTML reads and sets an element’s HTML markup, parsing the string into new elements, while textContent reads and sets plain text, with no markup.
Side-by-side comparison
#What it returns when read
- Element.innerHTML
- The content’s HTML markup as a string, with tags
- Node.textContent
- Plain text of the content and its descendants, with no tags
Assigning a string with tags
- Element.innerHTML
- Turns them into real HTML elements
- Node.textContent
- Displays the tags as plain text
Safety with data from a user
- Element.innerHTML
- Risk of an XSS attack, the data needs sanitizing
- Node.textContent
- Safe, text never turns into markup
Performance
- Element.innerHTML
- Slower, requires parsing HTML
- Node.textContent
- Faster, no markup parsing
Effect on existing child elements
- Element.innerHTML
- Destroys them along with attached event handlers
- Node.textContent
- Destroys them along with attached event handlers
Typical use
- Element.innerHTML
- Inserting a larger chunk of HTML known up front
- Node.textContent
- Displaying any text, including from a user
Example
#const box = document.querySelector('#box')
console.log(box.innerHTML)
console.log(box.textContent)
box.textContent = 'All tasks done'
console.log(box.innerHTML)Definition and usage
#Both properties read and overwrite an element’s contents, and assigning a new value always removes the existing children. What sets them apart is how they treat the string. innerHTML returns the HTML markup as text and parses the assigned string as HTML, turning it into real elements. textContent returns just the text of the element and its descendants, and inserts the assigned string literally, as a single text node.
That difference brings a risk with it. Inserting a string from a user or an API through innerHTML opens the door to an XSS attack, because the characters < and > turn into working markup. textContent is safe: the same characters show up on screen as text. innerHTML is also slower, because it requires parsing HTML, and it destroys event handlers attached to the old elements on every assignment.
Use textContent for displaying text, especially data from a user. Leave innerHTML for inserting larger chunks of HTML you know up front, for example a list built with a template literal, remembering to sanitize every value that comes from a user.
Syntax
#const html = element.innerHTML
element.innerHTML = htmlString
const text = element.textContent
element.textContent = newTextMore examples
#const list = document.querySelector('#list')
const firstItem = document.querySelector('#first')
list.innerHTML += '<li>Next step</li>'
console.log(document.querySelector('#first') === firstItem)
console.log(firstItem.isConnected)innerHTML += reads the whole HTML markup, glues on the new fragment, and parses everything again from scratch. The new #first element looks the same, but it’s a different object, and the old one is detached from the document.
const comment = document.querySelector('#comment')
comment.textContent = 'Use <strong> to make text bold'
console.log(comment.children.length)
console.log(comment.textContent)The string lands in the element literally, as a single text node. The characters < and > don’t create a tag, so children.length stays 0.
Good practices
#- Always use
textContentto display text from a user.innerHTMLwith unsanitized data is an open door to an XSS attack. - Avoid
innerHTML +=in a loop: every step rebuilds the entire content from scratch. Build the whole string first, or useinsertAdjacentHTML(). - To clear an element,
element.textContent = ""is enough, with no risk from HTML parsing.
Related entries
#- Element.innerHTMLReads or replaces an element’s content as HTML code.
- Node.textContentReads or sets all of an element’s text, without HTML markup.
- document.querySelector()Returns the first element that matches a CSS selector, or null when nothing matches.
- document.createElement()Creates a new HTML element with the given tag name, ready to be inserted into the page.
Spotted a mistake or missing an example? Write to us.