innerText vs textContent in JavaScript Explained

Understand the differences between innerText and textContent in JavaScript. Learn how they handle whitespace, hidden elements, and performance so you pick the right one every time.

JavaScriptbeginner
8 min read

Both innerText and textContent look like they do the same thing: get or set text on a DOM element. But they handle whitespace, hidden elements, and performance differently. Mixing them up can break code formatting, leak hidden content, or create performance bottlenecks. This article explains every difference with examples so you always pick the right property.

Quick Overview

Before diving into details, here is the core distinction:

  • textContent returns the raw text of every child node, including text inside hidden elements. It does not trigger a browser layout calculation.
  • innerText returns only the visible (rendered) text. It reflects what the user actually sees, respecting CSS like display: none and visibility: hidden. It triggers a layout reflow.
javascriptjavascript
// HTML:
// <div id="example">
//   Hello
//   <span style="display: none;">Secret</span>
//   World
// </div>
 
const el = document.getElementById("example");
 
console.log(el.textContent); // "Hello Secret World"
console.log(el.innerText);   // "Hello World"

Difference 1: Hidden Elements

The most important difference is how each property handles elements hidden by CSS.

javascriptjavascript
// HTML:
// <ul id="nav">
//   <li>Home</li>
//   <li>About</li>
//   <li style="display: none;">Admin Panel</li>
//   <li>Contact</li>
// </ul>
 
const nav = document.getElementById("nav");
 
// textContent includes ALL text, even hidden items
console.log(nav.textContent);
// "Home About Admin Panel Contact"
 
// innerText only includes VISIBLE text
console.log(nav.innerText);
// "Home\nAbout\nContact"

This distinction matters when building features like copy-to-clipboard or screen reader content. If you use innerText, hidden administrative links or debug information get excluded. If you use textContent, you get everything.

javascriptjavascript
// Practical example: copy visible text to clipboard
function copyVisibleText(element) {
  // innerText gives us only what the user sees
  const visibleText = element.innerText;
  navigator.clipboard.writeText(visibleText);
}
 
// Practical example: extract all text for indexing/search
function extractAllText(element) {
  // textContent gives us everything
  return element.textContent.toLowerCase().trim();
}

Difference 2: Whitespace Handling

The textContent property preserves whitespace exactly as it appears in the HTML source. The innerText property collapses whitespace to match how the browser renders the text on screen.

javascriptjavascript
// HTML:
// <p id="spaced">
//   This     has     extra
//   spaces   and    newlines
// </p>
 
const el = document.getElementById("spaced");
 
console.log(el.textContent);
// "\n  This     has     extra\n  spaces   and    newlines\n"
 
console.log(el.innerText);
// "This has extra spaces and newlines"

This is especially important for <pre> elements and code blocks:

javascriptjavascript
// HTML:
// <pre id="code">function hello() {
//   return "world";
// }</pre>
 
const code = document.getElementById("code");
 
// textContent preserves the exact formatting
console.log(code.textContent);
// "function hello() {\n  return \"world\";\n}"
 
// innerText may alter whitespace (browser-dependent)
console.log(code.innerText);
// "function hello() {\n  return \"world\";\n}"
PropertyLeading/trailing whitespaceMultiple spacesLine breaksTab characters
textContentPreservedPreservedPreservedPreserved
innerTextTrimmedCollapsed to single spaceMatching rendered layoutCollapsed

Difference 3: HTML Element Awareness

The innerText property understands block-level elements and inserts line breaks between them. The textContent property ignores element boundaries and concatenates all text nodes.

javascriptjavascript
// HTML:
// <div id="content">
//   <h2>Title</h2>
//   <p>First paragraph.</p>
//   <p>Second paragraph.</p>
// </div>
 
const content = document.getElementById("content");
 
// textContent: no separators between elements
console.log(content.textContent);
// "TitleFirst paragraph.Second paragraph."
 
// innerText: respects block boundaries with newlines
console.log(content.innerText);
// "Title\nFirst paragraph.\nSecond paragraph."

The innerText property also converts <br> elements to newline characters:

javascriptjavascript
// HTML:
// <p id="address">123 Main St<br>City, State 12345<br>Country</p>
 
const address = document.getElementById("address");
 
console.log(address.textContent);
// "123 Main StCity, State 12345Country"
 
console.log(address.innerText);
// "123 Main St\nCity, State 12345\nCountry"

Difference 4: Performance

This is where textContent has a clear advantage. Reading innerText forces the browser to calculate the current layout (a "reflow") to determine what is visible. Reading textContent simply walks the DOM tree and collects text nodes.

javascriptjavascript
const element = document.getElementById("large-content");
 
// Benchmark: read text 10,000 times
console.time("textContent reads");
for (let i = 0; i < 10000; i++) {
  const text = element.textContent;
}
console.timeEnd("textContent reads"); // ~5ms
 
console.time("innerText reads");
for (let i = 0; i < 10000; i++) {
  const text = element.innerText;
}
console.timeEnd("innerText reads"); // ~250ms (50x slower!)
OperationtextContentinnerText
Read (simple element)~0.001ms~0.02ms
Read (complex DOM, 1000 children)~0.1ms~5ms
Write (set text)~0.001ms~0.02ms
Triggers reflow on readNoYes
Triggers reflow on writeMinimalYes

For write operations, the performance gap is smaller. But for read-heavy operations like search filtering or text indexing, textContent is significantly faster.

Difference 5: Setting Text

When setting text, both properties replace all child nodes with a single text node. The behavior is similar, with one key distinction: innerText converts newline characters (\n) to <br> elements.

javascriptjavascript
const element = document.getElementById("output");
 
// textContent: newlines are NOT rendered as line breaks
element.textContent = "Line 1\nLine 2\nLine 3";
// Renders as: "Line 1 Line 2 Line 3" (single line in HTML)
 
// innerText: newlines ARE converted to <br> elements
element.innerText = "Line 1\nLine 2\nLine 3";
// Renders as three separate lines with <br> tags

Both properties escape HTML, making them safe from XSS:

javascriptjavascript
const userInput = '<script>alert("xss")</script>';
 
// Both are safe: HTML is escaped, not executed
element.textContent = userInput; // Shows the literal text
element.innerText = userInput;   // Also shows the literal text

When to Use Each Property

Use textContent When

javascriptjavascript
// 1. Setting plain text (most common use case)
document.getElementById("count").textContent = `${items.length} items`;
 
// 2. Reading text for processing (search, filtering)
const allText = element.textContent.toLowerCase();
const matches = allText.includes(searchTerm);
 
// 3. Reading code or pre-formatted content
const codeText = document.querySelector("pre code").textContent;
 
// 4. Performance-critical loops
items.forEach(item => {
  const text = item.textContent; // Fast, no reflow
  // ... process text
});
 
// 5. Accessibility: getting all text including hidden content
const fullText = article.textContent;

Use innerText When

javascriptjavascript
// 1. Getting text that matches the visual rendering
const displayedPrice = document.querySelector(".price").innerText;
 
// 2. Copying visible text to clipboard
async function copyToClipboard(element) {
  await navigator.clipboard.writeText(element.innerText);
}
 
// 3. Setting multi-line text with automatic <br> tags
const address = document.getElementById("address-display");
address.innerText = "123 Main Street\nApt 4B\nNew York, NY 10001";
 
// 4. Getting text with proper block-level line breaks
const articleText = document.querySelector("article").innerText;
// "Title\nFirst paragraph.\nSecond paragraph."

Common Mistakes to Avoid

Mistake 1: Using innerText in Performance-Critical Code

javascriptjavascript
// WRONG: innerText triggers reflow on every iteration
function filterList(searchTerm) {
  const items = document.querySelectorAll(".list-item");
  items.forEach(item => {
    const text = item.innerText.toLowerCase(); // Reflow each time!
    item.style.display = text.includes(searchTerm) ? "" : "none";
  });
}
 
// CORRECT: textContent avoids reflow
function filterList(searchTerm) {
  const items = document.querySelectorAll(".list-item");
  items.forEach(item => {
    const text = item.textContent.toLowerCase(); // No reflow
    item.style.display = text.includes(searchTerm) ? "" : "none";
  });
}

Mistake 2: Using textContent for Multi-Line Display

javascriptjavascript
// WRONG: textContent ignores newlines in the browser
const el = document.getElementById("output");
el.textContent = "Error on line 5\nMissing semicolon\nExpected }";
// Renders as single line: "Error on line 5 Missing semicolon Expected }"
 
// CORRECT: innerText converts \n to <br>
el.innerText = "Error on line 5\nMissing semicolon\nExpected }";
// Renders as three separate lines
 
// ALSO CORRECT: Use DOM methods
const lines = ["Error on line 5", "Missing semicolon", "Expected }"];
el.innerHTML = ""; // Clear
lines.forEach((line, i) => {
  if (i > 0) el.appendChild(document.createElement("br"));
  el.appendChild(document.createTextNode(line));
});

Mistake 3: Using textContent to Read User-Facing Text

javascriptjavascript
// HTML:
// <div class="product">
//   <span class="name">Widget Pro</span>
//   <span class="price">$29.99</span>
//   <span class="stock" style="display: none;">OUT OF STOCK</span>
// </div>
 
const product = document.querySelector(".product");
 
// WRONG: textContent leaks hidden stock status
console.log(product.textContent);
// "Widget Pro$29.99OUT OF STOCK"
 
// CORRECT: innerText shows what the user sees
console.log(product.innerText);
// "Widget Pro\n$29.99"

Real-World Example: Live Markdown Preview

This example uses both properties appropriately:

javascriptjavascript
function createMarkdownPreview(editorId, previewId) {
  const editor = document.getElementById(editorId);
  const preview = document.getElementById(previewId);
  const wordCount = document.getElementById("word-count");
 
  editor.addEventListener("input", () => {
    // Use textContent to read the raw input (preserves whitespace)
    const raw = editor.textContent;
 
    // Count words using textContent (fast, no reflow)
    const words = raw.trim().split(/\s+/).filter(Boolean).length;
    wordCount.textContent = `${words} words`; // Plain text update
 
    // Simple markdown-to-html conversion
    const html = raw
      .replace(/^### (.+)$/gm, "<h3>$1</h3>")
      .replace(/^## (.+)$/gm, "<h2>$1</h2>")
      .replace(/^# (.+)$/gm, "<h1>$1</h1>")
      .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
      .replace(/\*(.+?)\*/g, "<em>$1</em>")
      .replace(/`(.+?)`/g, "<code>$1</code>")
      .replace(/\n/g, "<br>");
 
    // Use innerHTML for the preview (controlled HTML, not user input)
    preview.innerHTML = html;
  });
 
  // Use innerText to get a clean copy of the rendered preview
  document.getElementById("copy-btn").addEventListener("click", () => {
    const visibleText = preview.innerText;
    navigator.clipboard.writeText(visibleText);
  });
}
 
createMarkdownPreview("editor", "preview");
Rune AI

Rune AI

Key Insights

  • Hidden content: textContent includes hidden elements; innerText excludes them based on CSS visibility
  • Whitespace: textContent preserves raw whitespace; innerText collapses it to match browser rendering
  • Performance: textContent is roughly 50x faster for reads because innerText triggers a layout reflow
  • Line breaks: Setting innerText converts \n to <br> elements; textContent does not render newlines
  • Default choice: Use textContent as your default for getting and setting plain text; switch to innerText only when you need rendering-aware output
RunePowered by Rune AI

Frequently Asked Questions

Is textContent or innerText better for accessibility?

The `textContent` property is generally better for programmatic text extraction because screen readers process their own rendering of the DOM, independent of either property. For building accessible interfaces, focus on proper semantic HTML and ARIA attributes rather than choosing between these two properties.

Does innerText work in all browsers?

Yes, `innerText` is supported in all modern browsers. It was originally an Internet Explorer-only feature that other browsers later adopted. It became part of the official HTML Living Standard. The `textContent` property is a W3C DOM standard and has been universally supported even longer.

Can I use textContent on form elements?

Form elements like `<input>` and `<textarea>` do not have child text nodes. Use the `value` property instead. The `textContent` property works on container elements like `<div>`, `<p>`, and `<span>` that can have child nodes. Setting `textContent` on an input element has no visible effect.

Which property triggers fewer DOM reflows?

The `textContent` property triggers fewer reflows. Reading `textContent` never forces a layout calculation because it just walks the DOM tree. Reading `innerText` always triggers a reflow because the browser must calculate which elements are visible and how whitespace collapses. For write operations, both trigger a reflow when the browser repaints.

Should I ever use innerHTML instead of these properties?

Use `innerHTML` only when you need to insert HTML markup that should be rendered (like bold text, links, or list items). For plain text, always use `textContent` (or `innerText` for multi-line display). Never use `innerHTML` with unsanitized user input because it creates XSS vulnerabilities.

Conclusion

The textContent and innerText properties serve different purposes despite their surface similarity. The textContent property gives you raw speed and access to all text including hidden content, making it the right default for most DOM text operations. The innerText property gives you rendering-aware text that matches what users see on screen, making it ideal for copying visible content or displaying multi-line text. Use textContent for setting plain text, processing text data, and performance-sensitive code. Use innerText when the visual rendering of the text matters.