Your First Unity 6 UI Toolkit HUD Row in 2026 - Theme Overrides Without Breaking Prefabs

2026 step-by-step Unity 6 UI Toolkit tutorial for first-time UI builders. Build one HUD row, theme it with USS variables, and avoid the prefab override regressions that re-broke jam projects in early 2026.

By GamineAI Team

Your First Unity 6 UI Toolkit HUD Row in 2026 - Theme Overrides Without Breaking Prefabs

You opened Unity 6, dropped a UIDocument into a scene, made a HUD row with a health label and a coin counter, and shipped it as a prefab. Five minutes later your teammate changed the panel settings and your prefab variants lit up yellow in the Hierarchy with "Override" markers all the way down. Half your prefabs now stamp the override every time they get touched. That sound you hear is your jam project dying.

This post is the boring, beginner-first walkthrough you should have found a month ago. We will build one HUD row using Unity 6 UI Toolkit, theme it with USS variables, and use the override pattern that does not regenerate your prefabs every time a teammate clicks "Apply." It is opinionated and short on theory; the goal is that you finish this post with a working asset and a habit that scales to a full HUD without a UI refactor sprint later.

Why this matters now (early 2026)

Three small things shifted in Unity 6 UI Toolkit defaults during the 2026.1 and 2026.2 doc updates, and together they revived an old pain point:

  • The Panel Settings asset gained a couple of new fields (sort order behavior, scale mode defaults) that most beginners now leave on auto. Saving a prefab that references a tweaked Panel Settings causes the prefab to record an override.
  • The default theme changed how TSS / theme style sheets stack on UIDocument. If you assign a theme on the prefab and on the scene, the scene wins - but the prefab now records a "phantom" override because the in-prefab theme assignment is not the same instance.
  • The #root element naming used in beginner docs got more attention as Unity rewrote the UI Builder tutorials in early 2026. Beginners following slightly older articles end up with name="root" elements clashing with auto-generated ones, which UI Builder marks as a difference even when the visuals match.

End result: lots of jam-team threads showing "Why does every save mark my HUD prefab dirty?" The fix is not exotic - it is structural. We will build the HUD row in a way that keeps the override-prone bits out of the prefab and the theme-prone bits inside USS variables.

If you are catching up on Unity 6 basics, skim our Unity 6.6 LTS upgrade safety sprint playbook first; otherwise you can keep reading.

What we will build in 30 minutes

A single horizontal HUD row that shows:

  • a health label (icon + number)
  • a coin counter (icon + number)
  • a timer (number)

It will use USS variables for color, padding, and font size; a single theme stylesheet for default values; and one prefab that references a UIDocument. Theme changes happen via a single USS variable override at runtime - no prefab gets dirty.

Skill level: you can open Unity 6, you have made a scene and a prefab, you have read about UXML and USS but never built one cleanly. That is enough.

Project setup (5 minutes)

  1. Create a new Unity 6 project (any template works; 2D Core or 3D URP both fine for this exercise).
  2. In Assets/, create a folder called UI/. Inside it, create:
    • Hud.uxml
    • HudTheme.tss
    • Hud.uss
    • HudController.cs
  3. Create a new UI Document GameObject in the scene (GameObject > UI Toolkit > UI Document).
  4. Save the scene; do not make a prefab yet. We will do that once the assets are clean.

If you have not made these asset types before, that is fine - UI Builder can create them via Window > UI Toolkit > UI Builder, then File > New > UXML Document and similar.

Step 1 - Author the UXML without a "root" trap

Open Hud.uxml in UI Builder. Beginner mistake number one is to name the outermost element root because old tutorials did that. In Unity 6's 2026 UI Builder, the canvas already exposes a #unity-builder-canvas and Panel Settings wraps everything in a root pseudo-element internally. Naming your top container root competes with internal naming and shows up as a "phantom override" later.

Use this structure:

<ui:UXML xmlns:ui="UnityEngine.UIElements">
  <ui:VisualElement name="hud-row" class="hud-row">
    <ui:VisualElement name="hud-health" class="hud-cell">
      <ui:VisualElement name="hud-health-icon" class="hud-cell__icon hud-cell__icon--health"/>
      <ui:Label name="hud-health-label" class="hud-cell__label" text="100"/>
    </ui:VisualElement>

    <ui:VisualElement name="hud-coins" class="hud-cell">
      <ui:VisualElement name="hud-coins-icon" class="hud-cell__icon hud-cell__icon--coins"/>
      <ui:Label name="hud-coins-label" class="hud-cell__label" text="0"/>
    </ui:VisualElement>

    <ui:VisualElement name="hud-timer" class="hud-cell">
      <ui:Label name="hud-timer-label" class="hud-cell__label hud-cell__label--mono" text="00:00"/>
    </ui:VisualElement>
  </ui:VisualElement>
</ui:UXML>

Three rules embedded in this snippet:

  • The outermost named element is hud-row, never root.
  • Element names are unique inside the document (hud-health, hud-coins, hud-timer).
  • BEM-style class names (hud-cell__icon--health) keep CSS specificity flat. Avoid deep selectors like .hud-row > .hud-health > .hud-cell__icon; they make USS hard to debug and easy to override accidentally.

Save the UXML. Do not assign it to the UIDocument yet - we want the styles wired first so the visual matches before the GameObject component connects.

Step 2 - Define a theme stylesheet (TSS) with variables only

Open HudTheme.tss. Theme Style Sheets in Unity 6 are stylesheets that hold variables. They are the single source of truth for color, spacing, and font size. The rule: anything that might be themed lives here as a USS variable; nothing else.

:root {
  --hud-color-text: rgb(238, 238, 238);
  --hud-color-text-muted: rgb(184, 184, 184);
  --hud-color-accent: rgb(255, 196, 84);
  --hud-color-health: rgb(231, 92, 92);
  --hud-color-coins: rgb(255, 196, 84);

  --hud-padding-cell: 8px;
  --hud-gap-cell: 12px;

  --hud-font-size: 18px;
  --hud-font-size-mono: 16px;

  --hud-icon-size: 16px;
}

Two things to notice:

  • Only variables. No selectors with concrete properties. That separation matters because the moment you put real selectors in a TSS, you lock visuals to one theme and lose the ability to swap.
  • The values are realistic for a 1080p HUD. Adjust later in the design pass; for now pick values that look correct under default Game view scaling.

Step 3 - Author the visual stylesheet (USS) that consumes those variables

Open Hud.uss. This is the file where real styles live, but every value that the theme owns is referenced via var(--...).

.hud-row {
  flex-direction: row;
  align-items: center;
  padding: var(--hud-padding-cell);
  -unity-font-style: normal;
}

.hud-cell {
  flex-direction: row;
  align-items: center;
  margin-right: var(--hud-gap-cell);
}

.hud-cell__icon {
  width: var(--hud-icon-size);
  height: var(--hud-icon-size);
  margin-right: 4px;
  background-color: var(--hud-color-text-muted);
  border-radius: 2px;
}

.hud-cell__icon--health {
  background-color: var(--hud-color-health);
}

.hud-cell__icon--coins {
  background-color: var(--hud-color-coins);
}

.hud-cell__label {
  color: var(--hud-color-text);
  font-size: var(--hud-font-size);
  -unity-text-align: middle-left;
}

.hud-cell__label--mono {
  font-size: var(--hud-font-size-mono);
}

Why this works:

  • Every theme-able property routes through var(--...). If you swap the theme later, no selector here changes.
  • Modifiers (--health, --coins, --mono) sit at low specificity. That keeps overrides predictable.
  • The icon background-color is a stand-in. When you have real icons, replace with background-image: url("project://database/Assets/UI/icons/health.png") or a Resources.Load URL.

Save the USS.

Step 4 - Wire the UIDocument and assign the theme exactly once

Back in the scene:

  1. Select the UI Document GameObject.
  2. In the inspector, drag Hud.uxml into Source Asset.
  3. Drag Hud.uss onto the UIDocument's Style Sheets list (use the small + button at the bottom).
  4. Open the Panel Settings asset already referenced by the UIDocument (Unity 6 creates one automatically named PanelSettings).
  5. In the Panel Settings inspector, set Theme Style Sheet to HudTheme.tss.

That last step is the critical one for 2026 workflows. Beginners commonly assign a theme on the UIDocument itself (which exists in the inspector if you tweak things) and also on Panel Settings, then end up with conflicting assignments that mark the prefab dirty. Assign the theme on Panel Settings only. Treat the UIDocument as a thin pointer.

Step 5 - Make a prefab, then make a variant - safely

Now drag the UIDocument GameObject from the Hierarchy into Assets/UI/Prefabs/. Unity creates a prefab. Open it in Prefab Mode and confirm:

  • UIDocument.SourceAsset is Hud.uxml
  • UIDocument.StyleSheets[0] is Hud.uss
  • UIDocument.PanelSettings references a shared PanelSettings asset that itself holds HudTheme.tss

Now make a prefab variant: select the prefab in the Project window, Create > Prefab Variant. Rename it HudPrefab_Variant_DarkTheme.

The variant inherits everything. Here is where most beginners get stuck: they double-click the variant, change the Panel Settings to point at a different theme, and save. The variant now records an override on Panel Settings - but the same Panel Settings asset is shared by other prefab variants, so the change leaks across them.

Do not change Panel Settings on the variant. Instead, do step 6.

Step 6 - Override theme variables, not theme assignments

Add a second USS file: HudDarkTheme.uss.

:root {
  --hud-color-text: rgb(232, 232, 232);
  --hud-color-text-muted: rgb(120, 120, 120);
  --hud-color-accent: rgb(120, 200, 255);
  --hud-color-health: rgb(255, 80, 120);
  --hud-color-coins: rgb(120, 200, 255);
}

In the variant prefab, add HudDarkTheme.uss to the UIDocument.StyleSheets list after Hud.uss. That single line is the only override the variant records on the UIDocument - one list entry.

This is the override pattern that scales. The variant says "use the base styles plus a small variable patch." It does not duplicate the base USS; it does not redefine selectors; it does not touch Panel Settings. When the base USS changes, the variant inherits the change automatically.

Success check: open Prefab Mode on the variant, save it, exit, and re-open. The variant inspector should show exactly one override marker, on the StyleSheets list. No phantom Panel Settings override, no scene theme tug-of-war.

Step 7 - Drive values from C# without re-stamping prefabs

Open HudController.cs:

using UnityEngine;
using UnityEngine.UIElements;

[RequireComponent(typeof(UIDocument))]
public class HudController : MonoBehaviour
{
    private Label _healthLabel;
    private Label _coinsLabel;
    private Label _timerLabel;
    private float _elapsed;

    private void OnEnable()
    {
        var root = GetComponent<UIDocument>().rootVisualElement;
        _healthLabel = root.Q<Label>("hud-health-label");
        _coinsLabel = root.Q<Label>("hud-coins-label");
        _timerLabel = root.Q<Label>("hud-timer-label");
    }

    public void SetHealth(int hp) => _healthLabel.text = hp.ToString();
    public void SetCoins(int c) => _coinsLabel.text = c.ToString();

    private void Update()
    {
        _elapsed += Time.deltaTime;
        var t = System.TimeSpan.FromSeconds(_elapsed);
        _timerLabel.text = $"{t.Minutes:00}:{t.Seconds:00}";
    }
}

Attach HudController to the prefab once. The controller queries elements by name (hud-health-label), not by class. Names are stable across themes; classes can be overridden. If you later rename an element in UXML, you change one string here.

Beginner gotcha: do not use root.Q<Label>(className: "hud-cell__label--mono"). Class-based queries match the first hit, which is fragile. Stick to name.

Step 8 - Sanity check with three deliberate breakages

A tutorial is only useful if you confirm the failure modes. Run these three breaks in the project; each one should leave the prefab clean:

  1. Edit Panel Settings. Change Scale Mode from Constant Pixel Size to Scale With Screen Size. Save. Open the variant prefab - it should not show a new override. Panel Settings is an asset; it is not part of the prefab's serialized data.
  2. Edit HudTheme.tss. Change --hud-color-accent to a different color. Save. Open base and variant prefabs - neither should mark a USS list override. TSS is referenced by Panel Settings, not the prefab.
  3. Edit Hud.uss. Change .hud-cell margin. Save. Open base prefab. The StyleSheets[0] entry references Hud.uss; the prefab does not care about the file's contents, only its asset reference. No new override.

If any of these three create overrides, you have a stray theme assignment on the UIDocument component itself. Open the prefab, clear UIDocument.PanelSettings.ThemeStyleSheet overrides, and use Panel Settings as the only theme owner.

What to avoid (and why)

  • name="root" on your top container. Conflicts with UI Builder 2026's internal naming.
  • Theme on the UIDocument and on Panel Settings. Pick one; production wisdom in 2026 is Panel Settings only.
  • Deep selectors like .hud-row > .hud-cell > .hud-cell__label. Specificity stacks; first variant override accidentally beats your base selector. Stay flat.
  • Hard-coded colors in Hud.uss. Any value you might want to theme later belongs in HudTheme.tss as a variable.
  • Class-based Q<T>() queries for elements you also style. Class selectors and element queries are different responsibilities.
  • Saving the prefab variant after changing Panel Settings. That is the override that re-broke half the jam threads. Use a small variant USS file with a :root { --... } block instead.

How this scales

This same pattern handles a full HUD plus a settings screen plus an in-game menu without a refactor:

  • One Hud.uxml per surface (or one Hud.uxml per panel if you prefer one document per screen)
  • One Hud.uss with all visual selectors that consume variables
  • One HudTheme.tss that holds variables
  • One Panel Settings asset per screen (or shared across screens) referencing HudTheme.tss
  • Variants only ever add a small *Theme.uss override file with :root { --... } blocks

If a designer wants to A/B test a darker theme, you ship a second HudTheme.uss and assign it via Panel Settings on a separate scene or via a UIDocument controller swap at runtime. No prefab changes.

Common jam-week beginner mistakes (and quick fixes)

"My HUD label is invisible."
Open the Game view, not the Scene view. UI Toolkit renders to the panel, not the scene 3D space.

"My HUD is gigantic at 1080p."
Panel Settings Scale Mode is set to Constant Physical Size (rare default but possible). Switch to Constant Pixel Size or Scale With Screen Size and set a reference resolution like 1920 x 1080.

"My theme variables don't change anything."
Confirm HudTheme.tss is assigned inside Panel Settings, not on the UIDocument. Then confirm your USS uses var(--name) not literal values.

"My variant inherits the base USS but the dark variables don't override."
The override USS must be later in the StyleSheets list than the base. UI Toolkit applies in order.

"Q<Label> returns null."
The element name must match exactly. Open the document in UI Builder and double-check the Name field (not the class).

"Save just stamped a Panel Settings override on every prefab."
You changed UIDocument.PanelSettings.ThemeStyleSheet while a prefab was open. Revert (click the gear on the property to revert), then re-edit Panel Settings directly as an asset.

A weekend extension when this works

Once the HUD row is clean, try these in order:

  1. Add a health-low state: in Hud.uss, add .hud-cell__label--low { color: var(--hud-color-health); }. Toggle via _healthLabel.AddToClassList("hud-cell__label--low") when health drops below a threshold.
  2. Add a breathing icon animation: in Hud.uss, add a transition: scale 200ms ease-in-out; to .hud-cell__icon--health. Update scale from C#.
  3. Add a settings panel as a new UIDocument in the same scene, using the same HudTheme.tss. Confirm variant overrides still only mark the StyleSheets list.

Each step reinforces the same pattern: USS variables are the source of truth, prefabs hold the wiring, controllers move data.

Outbound references for first-time UI Toolkit builders

What to read next on this site

Closing - the only HUD habit you need this week

Most "Unity 6 UI Toolkit broke my prefab" threads share one cause: a theme assigned in two places, with a deep selector trying to outrun the override. The fix is structural and small - Panel Settings owns the theme, USS variables own values, prefabs own wiring, variants override a single USS list entry only. Save those four sentences as a checklist next to your project window. Your jam prefab stops getting dirty, your designer can swap themes without breaking anyone, and your future self can refactor the HUD without rewriting six prefabs by hand.

If you build this in the next 30 minutes and the variant prefab still shows extra overrides, open Prefab Mode, revert every override that is not StyleSheets[1], save, and re-test. The fewer overrides a variant records, the less likely it is to break next week.