Data bindings connect native data to page HTML and CSS.

Your markup declares where each value appears and which buttons and input trigger which actions, and the library keeps the page in sync automatically.

This offers a number of benefits over syncing the page with your application via JavaScript:

| Benefit | Details |
|---|---|
| Faster performance | All logic is performed in native code, and layout / style re-calc is batched with data modifications. |
| Lower memory | Native data is bound directly to the DOM without any intermediate JavaScript values. |
| Easier to write | Keep styles and layout in HTML and CSS and keep the data model and logic in native code (hello MVC!). Use mock data to easily design pages in any browser. |
| Safer to use | Validate data bindings in HTML using automatic schema export from native code. |

## Going From DOM API to Data Bindings

Ultralight already offers API to manipulate JS and DOM directly from native code so why would you use data bindings?

The short answer is that the former tightly couples application logic to page presentation, which is bad for maintainability (what if you decide to change your page's layout or element ID names?).

Let's compare the two:

### With the DOM API

With the DOM API, native code updates each element itself every time data changes:

```cpp
document.getElementById("name").textContent = player.name();
document.getElementById("armor").textContent =
    std::to_string(player.stats().armor);
```

### With Data Bindings

With data bindings, your HTML and CSS markup declare where each value goes:

```html
<h1 ul-text="player.name"></h1>
<p>Armor: {{player.stats.armor}}</p>
```

Then in native code, you notify the engine whenever you want to broadcast data changes to the page (usually once per frame).

```cpp
ctx.Sync();
```

The markup looks familiar if you use Vue or Alpine.js in JavaScript, but the data lives in native code rather than in page scripts.

You can still use the [DOM API](/docs/2.0/about-the-dom-api) for one-off work like focusing a field or measuring an element— both approaches mix on one page.

## Getting Started

### 1. Describe Your Types

A [schema](/docs/2.0/describing-types-with-schemas) lists the fields that native code exposes to the page and the actions the page can fire in native code.

Include `<Ultralight/dom/data/Context.h>` to access the data binding types (they aren't included by `<Ultralight/DOM.h>`).

Plain structs bind automatically by member name without a schema, while existing classes declare a `dd::TypeTraits` specialization:

```cpp
#include <Ultralight/dom/data/Context.h>

namespace dd = ultralight::dom::data;

// A plain struct binds as-is: stats.health, stats.armor.
struct Stats {
  int health = 100;
  int armor = 0;
};

// An existing class from your game.
class Player {
 public:
  const std::string& name() const;
  const Stats& stats() const;
  void TakeDamage(int amount);
  void UsePotion();
};

template <> struct dd::TypeTraits<Player> {
  static constexpr auto schema = dd::Schema(
      dd::Field("name", &Player::name),     // reads player.name()
      dd::Field("stats", &Player::stats),   // a nested object
      dd::Action("usePotion"));             // an action the page can fire
};
```

### 2. Bind and Attach

A `dd::Context` connects native objects to the pages loaded by one or more `View` instances.

Call `Bind()` to register an instance under a name the page can use, and chain handlers to receive actions from the page into native code:

```cpp
dd::Context ctx = dd::Context::Create();
if (!ctx.AttachTo(view.get()))
  Log("couldn't attach the data context");

Player& player = game.LocalPlayer();              // your existing object
dd::Binding binding = ctx.Bind("player", player)  // the page calls it "player"
    .OnAction<"usePotion">(&Player::UsePotion);   // the button below calls this

view->LoadURL("file:///character.html");
```

### 3. Use It in the Page

In HTML, you reference data with [`ul-*` attributes and `{{path}}` text](/docs/2.0/showing-data-in-markup):

```html
<div class="character-panel">
  <h1 ul-text="player.name"></h1>
  <p>Armor: {{player.stats.armor}}</p>
  <progress max="100" ul-attr:value="player.stats.health"></progress>
  <button ul-on:click="player.usePotion">Drink Potion</button>
</div>
```

### 4. Change Your Data and Sync

Call `Sync()` once per frame to deliver page events to native handlers and push data to the page:

```cpp
player.TakeDamage(10);
ctx.Sync();  // runs UsePotion() if clicked, then updates the page
```

Each call to `Sync()` performs three operations in order:

1. Delivers events from the page to native handlers (changes first, then actions).
2. Reads every bound object through its schema.
3. Broadcasts changed values from native code to the page in a single batch.

## How Data and Input Flow

Data flows from native code to the page, while requests such as actions and change requests flow from the page to native code.

| Direction | What Travels | Markup | Native Code |
|---|---|---|---|
| Native code to the page | [Data](/docs/2.0/showing-data-in-markup) (fields, [lists](/docs/2.0/binding-lists), and CSS variables) | `{{path}}`, `ul-text`, `ul-attr`, `ul-class`, `ul-style`, `ul-var`, `ul-show`, `ul-if`, `ul-for` | Modify native objects, call `Sync()` |
| Page to native code | [Actions](/docs/2.0/handling-actions) (clicks or other events) | `ul-on:click` | `OnAction()` |
| Page to native code | [Value changes](/docs/2.0/binding-form-controls) in form fields | `ul-value` | `OnChange()` |

### Two-Way Binding with `ul-value`

The only two-way binding is `ul-value` (eg, a slider bound to `settings.volume`). It works as a round trip rather than a shared variable:

1. The user moves the slider on the page. The page reflects the new position immediately and sends a change request from the page to native code.
2. The `OnChange()` handler validates the data (potentially modifying / blocking it) and updates the corresponding value in native data. The next `Sync()` sends the confirmed value back to the page.

## Designing Pages with Mock Data

You can build and style data-bound pages in any browser without running the application.

The generator creates mockup files from the application's schema so the page displays real model data. It can also check markup against the schema in a build step— typos in binding paths get caught before the page runs.

1. Save the schema and current values of every bound model from native code (export this in development builds only):

```cpp
ctx.DumpSchema("ui/schema.json");  // written at the end of the next Sync()
```

2. Run `gen-mock-data.py` from the SDK root folder (the directory holding `tools/`, not from inside `tools/scripts`):

```bash
python tools/scripts/gen-mock-data.py ui/schema.json -o ui/
```

This creates the mockup scripts beside the pages. The `mock-data.js` file is written only once, so future runs preserve any custom mock data added to it.

3. Add the mock script tag to the page:

```html
<script src="ul-mock.js"></script>
```

Open the HTML file in any browser to inspect the page with the saved values. The script loads nothing inside Ultralight, so the tag can stay in the shipped page.

To define custom mock data, handle actions, test scenarios, or run the `check` build step, see [Mocking Bound Pages](/docs/2.0/mocking-bound-pages).

For C projects, see [Data Bindings in C](/docs/2.0/c-data-bindings).
