You can query and modify elements using JavaScript's DOM API directly from C++, with the same call names and meaning. The examples name the View's `dom::Document` as `document` (see [About the DOM API](/docs/2.0/about-the-dom-api)), so the code reads like page script.

## Finding Elements

You can query elements by ID, CSS selector, or relationship to another element.

```cpp
auto status = document.getElementById("status");
auto title = document.querySelector(".panel h1");
auto panel = title.closest(".panel");

for (auto item : document.querySelectorAll("#log li"))
  item.classList.add("seen");
```

When a query finds nothing, it returns an empty handle— calls on it do nothing (see [DOM Handles and Errors](/docs/2.0/dom-handles-and-errors)).

## Changing Content

You can update or read an element's text or HTML markup by assigning to its properties.

```cpp
status.textContent = "Connected";
status.innerHTML = "<b>Connected</b>";

std::string text = status.textContent;
```

Assigning to `textContent` replaces the element's children with text.

Assigning to `innerHTML` replaces them with parsed markup (scripts in the markup never run).

> 👍 Store Reads in std::string
>
> Read string properties into an explicit `std::string`. The property is a proxy, so `auto` doesn't compile.

> 🚧 Pass Untrusted Text to textContent
>
> Pass text from users or the network to `textContent`, never `innerHTML`. Markup assigned to `innerHTML` keeps event attributes like `onclick`, which run when JavaScript is enabled.

> 🚧 Reading innerText Triggers Layout
>
> Reading `innerText` forces a synchronous style and layout pass when the page has pending changes. Prefer `textContent` when you don't need the rendered text, and make all changes before reading.

## Classes and Attributes

You can modify an element's classes, attributes, and custom dataset properties.

```cpp
auto link = document.querySelector("a.help");
link.classList.add("highlight");
link.classList.toggle("visited");
link.setAttribute("href", "https://ultralig.ht");
link.dataset["topic"] = "setup";  // sets data-topic="setup"

if (auto target = link.getAttribute("target"))
  Log(*target);
```

`getAttribute()` returns a `std::optional`— it is `std::nullopt` when the attribute is missing.

## Setting Styles

You can set an element's styles by assigning to properties on `style` using their camelCase JavaScript names.

```cpp
auto bar = document.getElementById("health-bar");
bar.style.width = "50%";
bar.style.opacity = 0.5;
bar.style.left = dom::StyleValue::Px(x);
```

String literals are checked at compile time— a mistyped number (eg, `"50pxx"`) causes a compile error, while a string built at runtime that fails to parse is ignored and keeps the old value.

For values you update every frame, use unit helpers like `dom::StyleValue::Px()` to avoid formatting strings. The renderer still validates the value against the property's CSS grammar— an invalid assignment is ignored and keeps the old value.

To read computed styles and work with units, see [Reading and Writing Styles](/docs/2.0/reading-and-writing-styles). To measure elements and control scrolling, see [Element Geometry and Scrolling](/docs/2.0/element-geometry-and-scrolling).

## Adding and Removing Elements

You can create new elements, insert them into the document, and move or remove existing nodes.

```cpp
auto log = document.getElementById("log");
auto entry = document.createElement("li");
entry.textContent = "Saved";
log.appendChild(entry);

entry.remove();          // out of the page, still valid
log.appendChild(entry);  // back in

auto copy = entry.cloneNode(true);
log.insertBefore(copy, entry);
```

Calling `remove()` takes an element out of the page— the handle stays valid, so you can insert it back in later.

Calling `appendChild()` on an element already in the document moves it from its existing position.

### Inserting Nodes and Text

You can insert nodes and text at specific positions relative to an element.

| Call | Where It Inserts |
| :--- | :--- |
| `append(...)` | Inside the element, after its last child |
| `prepend(...)` | Inside the element, before its first child |
| `before(...)` | Just before the element (as a sibling) |
| `after(...)` | Just after the element (as a sibling) |
| `replaceWith(...)` | In place of the element |

Each call takes any mix of elements and strings— strings are inserted as text (never parsed as markup).

```cpp
auto entry = document.createElement("li");
entry.append(icon, " Saved at ", time_text);
list.prepend(entry);

entry.after(divider);
old_entry.replaceWith(entry);
```

## Building UI from Your Data

To populate a list from a C++ vector, build the rows off the page in a document fragment and pass the fragment to `replaceChildren()`— it swaps out the old rows in one call.

```cpp
void ShowScores(dom::Document document, const std::vector<Score>& scores) {
  auto rows = document.createDocumentFragment();
  for (const Score& score : scores) {
    auto row = document.createElement("li");
    row.append(score.name, ": ", std::to_string(score.points));
    rows.appendChild(row);
  }
  document.getElementById("scores").replaceChildren(rows);
}
```

> 📘 Fragments Batch Tree Changes
>
> Using a document fragment adds every child node in a single tree mutation, so a `MutationObserver` receives only one record. Appending elements individually would not run layout per row either— layout always waits for the next layout read or rendering update.

## Differences from JavaScript

While the C++ DOM API mirrors JavaScript, a few types and behaviors differ.

| JavaScript | C++ |
| :--- | :--- |
| Read-only properties (`el.parentElement`) | Calls (`el.parentElement()`) |
| `getAttribute()` returns `null` when missing | Returns `std::nullopt` |
| `el.dataset.userId` | `el.dataset["userId"]` |
| `el.children` is a live collection | `children()` and `querySelectorAll()` return snapshots (query again after changes) |
| Failing calls throw (bad selector or invalid tree change) | Returns an empty value and logs the error. Pass `dom::Checked` for the reason (see [DOM Handles and Errors](/docs/2.0/dom-handles-and-errors)). |

To react to clicks on the rows, see [Handling DOM Events](/docs/2.0/handling-dom-events).
