---
title: "Change a rendered HTML page from C#"
url: https://recursiveai.org/onerender/cookbook/update-page/
description: "Update a rendered HTML page from C#: set text, toggle a class and disable a button without rebuilding the page or losing focus, hover or form state. Runnable code with OneRender."
version: "1.3.1"
---

# Change a rendered HTML page from C#

Find the element on the live page and change it in place: set its text, toggle a class, set an attribute. OneRender restyles and repaints only what changed, and focus, hover and form state stay as they were.

```shell
dotnet new console -o UpdatePage && cd UpdatePage
dotnet add package RecursiveAI.OneRender.Image --version 1.3.1
```

Put this in `Program.cs` and run `dotnet run`:

```csharp
using OneRender;
using OneRender.Image;
using OneRender.Interaction;
using OneRender.Painting;
using RecursiveAI.OneHtml;

string html = """
<!doctype html>
<html lang="en">
<style>
  body { margin: 0; font: 16px sans-serif; background: #16202c; color: #e8edf3 }
  .hud { display: flex; align-items: center; gap: 12px; padding: 12px 16px }
  #score { font-weight: bold; min-width: 4em }
  button { font: inherit; color: inherit; padding: 6px 12px; border: 0;
           border-radius: 6px; background: #2a3644 }
  button.on { background: #3b82f6 }
  button:disabled { opacity: .4 }
</style>
<div class="hud">
  <span>Score</span><span id="score">0</span>
  <button id="sound">Sound</button>
  <button id="save">Save</button>
</div>
</html>
""";

InteractivePage ui = InteractivePage.Create(html,
    new HtmlPageOptions(FontProviders.CreateDefault())
    { ViewportWidth = 360, ViewportHeight = 60 });
Node score = ui.FindElement("score");
Node sound = ui.FindElement("sound");
Node save = ui.FindElement("save");
ui.Focus(save);

// Text: cheap enough to change every frame.
InputOutcome o = ui.SetTextContent(score, "1250");
Console.WriteLine($"score: {o}, \"{ui.GetTextContent(score)}\"");

// Classes and attributes: for state changes, not every frame.
o = ui.ToggleClass(sound, "on", force: true);
Console.WriteLine($"sound: {o}, class \"{ui.GetClassName(sound)}\"");
o = ui.SetAttribute(save, "disabled", "");
string focused = ui.FocusedElement.IsNull ? "none" : ui.FocusedElement.Id;
Console.WriteLine($"save: {o}, disabled \"{ui.GetAttribute(save, "disabled")}\", "
    + $"focused: {focused}");
string original = save.GetAttribute("disabled") ?? "null";
Console.WriteLine($"the Node handle still reads the original: {original}");

var list = new DisplayList();
ui.Page.Paint(list);
File.WriteAllBytes("hud.png", DisplayListRasterizer.Render(list, 360, 60).ToPng());
```

```text
score: Relaid, "1250"
sound: Relaid, class "on"
save: StatesChanged, Relaid, FocusChanged, disabled "", focused: none
the Node handle still reads the original: null
```

In `hud.png` the score reads 1250, Sound is blue and Save is dimmed by its `:disabled` rule.

## How it works

- **Ten calls on `InteractivePage`.** `SetTextContent`, `SetClassName`, `AddClass`, `RemoveClass`, `ToggleClass`, `SetAttribute` and `RemoveAttribute` change the page; `GetTextContent`, `GetClassName` and `GetAttribute` read it back. Each change returns an `InputOutcome`: `Relaid` when something changed (call `Repaint` as in [Hit testing and events](https://recursiveai.org/onerender/cookbook/hit-testing-events/)), `None` for a no-op.
- **The result equals a fresh page.** A page created from the edited HTML lays out and paints the same. Focus, hover, scroll positions, form state and listeners stay as they were, and a focused element that stops being focusable loses focus, with the usual `blur` events.
- **Your `Node` handles stay valid**, but they point into the document the page was created from, which never changes. Read current values through the page.
- **`SetTextContent`** takes an element that holds exactly one text node, like a label or a readout. It throws on `style`, `script`, `textarea`, `title`, `option` and an element with more than one child.

## What each change costs

- **Text** doesn’t touch the cascade: only the changed text is laid out and painted again. Once warm, a change that fits the room the old text had allocates nothing, so it’s safe to do every frame.
- **Classes and attributes** restyle from a new snapshot of the document: about 0.8 MB per change on a 1,800-node page. Attributes a control reads when its boxes are made (`value`, `checked`, `type`, `src`) rebuild the page, about 7 MB there.
- **So** change text, `transform` and `opacity` per frame, and classes and attributes when something happens, like a click or a state change.

The full rules, including what `checked` and `value` do after the user has changed a control, are in [Game hosts](https://recursiveai.org/onerender/docs/game-hosts/#changing-text-classes-and-attributes-after-creation).
