---
title: "Extract visible text from HTML"
url: https://recursiveai.org/onehtml/cookbook/extract-visible-text/
description: "Get the readable text of a page without its script and style contents, either with a short walk over the text nodes or with the cascade-aware reading…"
version: "1.0.0"
---

# Extract visible text from HTML

Get the readable text of a page without its script and style contents, either with a short walk over the text nodes or with the cascade-aware reading view, which also drops content hidden by CSS, the hidden attribute or aria-hidden.

`TextContent` follows the DOM standard, which includes the text of `script` and `style` elements. That is correct, but it is rarely what you want for search indexing, previews or diffs. There are two ways to get the readable text.

```csharp
using System.Text;
using RecursiveAI.OneHtml;
using RecursiveAI.OneHtml.Css;
using RecursiveAI.OneHtml.Accessibility;

Document doc = Document.Parse(File.ReadAllBytes("events.html"));
Node body = doc.Root.QuerySelector("body");

// TextContent follows the DOM standard: script and style text is included
Console.WriteLine($"TextContent has the script: {body.TextContent!.Contains("addEventListener")}");

// Walk the text nodes yourself and skip the elements that never render text
var text = new StringBuilder();
foreach (Node node in body.Descendants)
{
    if (node.Kind == NodeKind.Text && node.ParentElement.Closest("script, style, template, noscript").IsNull)
    {
        text.Append(node.Data);
    }
}
string plain = string.Join(' ', text.ToString().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries));
Console.WriteLine(plain);

// Or let the cascade decide: the reading view drops display:none, hidden and aria-hidden content
Document page = Document.Parse("""
    <style>.promo { display: none }</style>
    <h1>Opening hours</h1>
    <p class="promo">Members save 10%</p>
    <p hidden>Draft: holiday hours</p>
    <p>Monday to Friday, 9 to 6. <span aria-hidden="true">★</span></p>
    <script>console.log("not text")</script>
    """);
var tree = AccessibilityTree.Build(page, DocumentStyles.Create(page));
Console.WriteLine(tree.CreateReadingView().ToText());
```

```text
TextContent has the script: True
Books Events Visit Upcoming events Story time for ages 3–5 Saturdays at 10:30 in the children’s room. Details Repair café Bring a lamp, a toaster or a torn jacket. Volunteers fix what they can. Partner site © 2026 Harbor Street Library · hello@example.org Back to top
heading level 1: Opening hours
Monday to Friday, 9 to 6.
```

**The text-node walk** is fast and needs only the parser. It skips `script`, `style`, `template` and `noscript`, so the inline script at the end of the events page is gone. It knows nothing about CSS: text inside `display: none` or `hidden` would still be there. Use `node.ParentElement.Closest(...)`, because `Closest` starts from an element and a text node isn’t one.

**The reading view** reads the page from its accessibility tree, which is built with the cascade. It leaves out text hidden by `display: none`, `visibility: hidden`, `content-visibility: hidden`, the `hidden` attribute, a closed `details`, `inert` and `aria-hidden`, and it doesn’t read script. Each line is a step: text runs, plus announced elements such as `heading level 1: Opening hours`. Headings and other announced elements come through as element items rather than `ReadingItemKind.Text` items, so keep their text too if you want every visible word; [Extract visible text from HTML in C#](https://recursiveai.org/onehtml/articles/extract-visible-text-from-html-csharp/) shows a complete version and compares HtmlAgilityPack and AngleSharp.

Limits of the reading view:

- It decides from markup and CSS alone. Hiding that depends on layout, such as zero size, clipping or off-screen positioning, isn’t known, so that text is still read. Chrome doesn’t hide such content from its accessibility tree either.
- The wording is generic English. It is an approximation, not what any particular screen reader says. [Reading view](https://recursiveai.org/onehtml/docs/reading-view/) has the rules.
- Script doesn’t run. For a page built by script, parse the browser’s serialized DOM instead of the HTML the server sent.
