> For the complete documentation index, see [llms.txt](https://postica.gitbook.io/binding-system-3/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://postica.gitbook.io/binding-system-3/tutorials/uitoolkit-hud.md).

# A HUD in UI Toolkit

Bind a UXML HUD from UI Builder, stored in the scene or in the file.

UI Toolkit separates the document from the scene, which is good for reuse and awkward for wiring: the label is in a `.uxml` file and the number it should show is on a component in a scene.

The package closes that gap from inside UI Builder. Right click a property, bind it, done. The part worth paying attention to is **where the binding is kept**, because there are two answers and the package picks one for you.

## What you will build

A HUD with a score label and a health bar, bound from UI Builder to a component in the scene. Then a second document, bound the other way, to see both storage modes.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2Fzyg1LXipcRDzUe8ROe1B%2FScreenshot%202026-09-26%20at%2016.56.51.png?alt=media&amp;token=92e8ade6-58fb-4cdb-88d1-bd6370c0975f" alt=""><figcaption><p>A bound UXML HUD running over the scene</p></figcaption></figure>

**About 20 minutes.**

## What you need

A project with UI Toolkit available, which is every Unity 6 project. One component with a couple of fields, provided in step 1.

## 1. Something to display

Create `PlayerStats.cs` and put it on a GameObject called `Player`:

{% code title="PlayerStats.cs" %}

```csharp
using UnityEngine;

public class PlayerStats : MonoBehaviour
{
    public int score = 0;
    [Range(0f, 100f)] public float health = 100f;
}
```

{% endcode %}

## 2. Make the document

Create the UXML with **Assets ▸ Create ▸ UI Toolkit ▸ UI Document** and name it `HUD`.

Double click it to open **UI Builder**. In the Library panel, drag in:

* a **Label**, and set its **Name** to `score-label`
* a **ProgressBar**, and set its **Name** to `health-bar`

Save.

{% hint style="info" %}
Name the elements. A scene-stored binding can find an element by its hierarchy path, but an asset-stored one has only the name to go on, and step 7 uses that. Naming them now means either storage mode works.
{% endhint %}

## 3. Put it in the scene

Create **GameObject ▸ UI Toolkit ▸ UI Document**. Select it and set **Source Asset** to `HUD`.

If the **Panel Settings** field is empty, create one with **Assets ▸ Create ▸ UI Toolkit ▸ Panel Settings Asset** and assign it. Nothing renders without it.

The label and the bar now appear in the Game view.

## 4. Bind the score label

Go back to **UI Builder** with `HUD` open.

Look at the viewport. There is a pill reading **Currently binding for UIDocument**, naming the GameObject you just made. That is telling you where the next binding will be stored, before you make it.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2F74Dt0TQB1OvWHIIGGeCz%2FScreenshot%202026-09-26%20at%2016.23.51.png?alt=media&amp;token=61554c24-935d-4080-b2b4-55a34ccf8aa9" alt="" width="563"><figcaption><p>The scene-link pill, naming the GameObject that will hold the binding</p></figcaption></figure>

Select `score-label`. In UI Builder's Inspector, find the **Text** field, **right click it**, and choose **Add BS3 Binding**.

The familiar bind popup opens, already knowing the element, the property and that it wants a `string`. Drag the `Player` GameObject onto the row and pick **PlayerStats ▸ score**.

An `int` is not a `string`, so a converter is chosen for you. Tick **UPDATE** in **Update Points ▸ Update On** if it is not already on, and close the popup.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2F2g9FuYLLs68yV2rjJ0Gg%2FScreenshot%202026-09-26%20at%2016.30.10.png?alt=media&amp;token=4cae4fb2-4625-43f6-a21b-adcd667fc952" alt="" width="563"><figcaption><p>The bind popup, opened from a UI Builder property field and prefilled</p></figcaption></figure>

The **Text** row in UI Builder now carries the Binding System hexagon, so bound properties are visible at a glance.

## 5. Bind the health bar

Select `health-bar`. Right click its **Value** field, **Add BS3 Binding**, and point it at **PlayerStats ▸ health**.

A `ProgressBar` runs 0 to 100 by default and so does `health`, so no modifier is needed.

Press play and change `score` and `health` on the Player. Both update.

{% hint style="warning" %}
Nothing moves inside the UI Builder canvas itself, and that is deliberate. A binding's source cannot resolve in an authoring context, so they are held inert there rather than showing every field as broken. The Game view is where you see them work.
{% endhint %}

## 6. Look at where those bindings went

Select the **UIDocument** GameObject. There is a second component on it now: **UIDocumentBindings**, added when you made the first binding.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FdKnDRvJysuyM2IRowYbE%2FScreenshot%202026-09-26%20at%2016.32.29.png?alt=media&amp;token=caaa09a4-2888-41b3-aaa3-04e992819370" alt="" width="563"><figcaption><p>The UIDocumentBindings component, holding both scene bindings</p></figcaption></figure>

It lists both entries, each with an element picker, a property picker and a full bind row, so everything is editable here as well as in UI Builder. It also validates: clear the document's **Source Asset** and the component says so, in as many words.

Now open `HUD.uxml` in a text editor. **There is nothing about binding in it.** The bindings are in the scene, on that GameObject, and the document is untouched.

That is the default because it is the more capable option: only a scene-stored binding can name `Player`, because a `.uxml` file cannot hold a reference to a scene object.

## 7. Bind a document the scene does not use

Create a second UXML, **Assets ▸ Create ▸ UI Toolkit ▸ UI Document**, named `Watermark`. Open it in UI Builder and drag in a **Label** named `version-label`.

Do not create a UIDocument for it. Nothing in the scene uses this file.

Look at the viewport: there is no pill this time, because there is no scene document to bind for.

Right click the label's **Text** field and choose **Add BS3 Binding**. Open the source view, set the mode to **From Static Value**, pick the `Application` type and then `version`.

Save, and open `Watermark.uxml` in a text editor:

```xml
<engine:Label name="version-label">
  <Bindings>
    <bs3:BS3DataBinding property="text" bind-data="{...}" />
  </Bindings>
</engine:Label>
```

This binding is **in the file**. It travels with the asset, it works in any scene that loads the document, and it is shared by every user of it. What it cannot do is name a scene object, which is why the first document did not get this treatment.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FksQ5V4yKt612G2ZjWddx%2FScreenshot%202026-09-26%20at%2016.52.17.png?alt=media&amp;token=46a04f66-0081-4d50-a429-ac576115a3d8" alt=""><figcaption><p>An asset-stored binding, written into the UXML as a BS3DataBinding tag</p></figcaption></figure>

## What just happened

**The storage decision is made from the scene, not from you.** When you right click a property, the package looks for scene `UIDocument`s using the open UXML. Exactly one, and the binding goes there with no prompt. Several, and it asks which. None, and it writes into the file. The pill in the viewport shows you which of the three you are in before you click.

**Both kinds coexist.** The `UIDocumentBindings` inspector has a **UXML Asset Bindings** foldout listing the bindings embedded in the file alongside its own, read-only, with a Remove button. One place to see everything driving a document.

**A bound property is a real UI Toolkit binding.** `BS3DataBinding` is a `CustomBinding`, the same extension point Unity's own runtime bindings use. It is registered on the element, it participates in the panel's update, and it can be created from code with `element.SetBinding(...)`.

**Writes work too.** A read pushes the source into the element. A write pushes element changes back, through change events when the property is the element's `value` slot and by polling otherwise. So a UI Toolkit `Slider` bound `ReadWrite` is a two-way control, exactly like the uGUI one in [the settings panel tutorial](/binding-system-3/tutorials/settings-panel.md).

**Editing scene bindings in UI Builder needs a bridge.** UI Builder only knows about files, and your first two bindings are on a component. The **Edit in UI Builder** button on `UIDocumentBindings` starts a session that injects the component's entries into the real `.uxml` as temporary tags, reconciles the component from the file on every save, and strips the tags when you press **Finish Editing**. It is crash safe by construction: the component's list is never cleared, so at every instant either the component alone or the component plus the tags holds the data. Entering play mode also ends a session, because object references travel through the tags as instance ids that only survive one editor run. [UI Toolkit and UXML](/binding-system-3/overview/ui-toolkit.md) has the details.

## Try changing this

**Right click a bound property again.** The menu now offers **Edit BS3 Binding** and **Remove BS3 Binding** instead of Add, so a duplicate is not something you can create by accident. Remove is offered even for a half-finished binding, which makes undoing a mistake one right click.

**Add a second UIDocument using the same HUD.** Now **Add BS3 Binding** lists both and asks which GameObject should store the binding, and the viewport pill counts them instead of naming one. The two instances then carry independent bindings, which is how one HUD asset serves a split-screen game.

**Point the watermark at a bind variable instead of `Application.version`.** It still works, and it is still asset-stored. An asset binding is not limited to statics: variables, project assets and static members are all reachable. The only thing out of reach is an object that exists in one scene.

**Rename `score-label` after binding it.** The scene binding survives, because it can fall back on the hierarchy path. Do the same in `Watermark` and the asset binding breaks, because a `.uxml` tag has only the name. The `UIDocumentBindings` validation reports it rather than leaving you guessing.

## Related pages

* [UI Toolkit and UXML](/binding-system-3/overview/ui-toolkit.md): the full feature, the round-trip session, and the troubleshooting table.
* [TextMeshPro and Unity UI](/binding-system-3/reference/integrations/unity-ui.md): the same job in uGUI.
* [Sources](/binding-system-3/overview/sources.md): static values, variables, and what an asset binding can reach.
* [Proxy Bindings](/binding-system-3/overview/proxy-bindings.md): the storage rule everywhere else.
