Mastering Unity’s UI Toolkit: Access UXML Elements Easily

Introduction

Unity’s UI Toolkit provides a modern way to create user interfaces using UXML, USS, and C#.

Instead of creating every UI element directly in C#, you can define your interface using UXML and then access those elements from your scripts at runtime.

A typical UI Toolkit workflow looks like this:

UXML
  ↓
UIDocument
  ↓
rootVisualElement
  ↓
Q() / Query()
  ↓
C# Script
  ↓
Events & UI Logic

In this guide, we’ll look at several practical ways to access UI elements from C#.


1. Accessing UI Elements Using UIDocument and UXML

The most common approach is to use the UIDocument component.

A UIDocument loads a UXML hierarchy and exposes its root through:

rootVisualElement

From there, you can search for individual elements.

Step 1: Create Your UXML

Create a UXML document containing elements such as:

  • Button
  • Label
  • TextField
  • Image
  • VisualElement

For example:

<ui:UXML
    xmlns:ui="UnityEngine.UIElements">

    <ui:Label
        name="myLabel"
        text="Welcome!" />

    <ui:Button
        name="myButton"
        text="Click Me" />

</ui:UXML>

The important part is the name attribute:

name="myButton"

This allows the element to be found from C#.


2. Accessing UXML Elements From C#

Attach a UIDocument component to a GameObject and assign your UXML document.

Then use:

using UnityEngine;
using UnityEngine.UIElements;

public class UIToolkitExample : MonoBehaviour
{
    private UIDocument uiDocument;

    private void OnEnable()
    {
        uiDocument = GetComponent<UIDocument>();

        VisualElement root = uiDocument.rootVisualElement;

        Button button = root.Q<Button>("myButton");
        Label label = root.Q<Label>("myLabel");

        button.clicked += () =>
        {
            Debug.Log("Button clicked!");
        };

        label.text = "Hello, UI Toolkit!";
    }
}

Now your C# script can communicate directly with the elements defined in UXML.


3. Understanding rootVisualElement

The rootVisualElement is the entry point to your UI hierarchy.

Think of it as the parent container for the UI loaded by the UIDocument.

For example:

UIDocument
    │
    └── rootVisualElement
          │
          ├── Label
          ├── Button
          ├── TextField
          └── Image

You can start your queries from:

var root = uiDocument.rootVisualElement;

Then search inside the hierarchy.


4. Using Q<T>() to Find an Element

The Q<T>() method is one of the most convenient ways to retrieve a specific UI element.

For example:

Button button = root.Q<Button>("myButton");

Here:

  • Button specifies the expected element type.
  • "myButton" is the element’s name.

Similarly:

Label label = root.Q<Label>("myLabel");

TextField textField = root.Q<TextField>("myTextField");

Image image = root.Q<Image>("myImage");

This is particularly useful when you know exactly which element you want.


5. Querying Without a Name

You can also search for the first element of a specific type.

For example:

Image image = root.Q<Image>();

This searches the hierarchy for an Image.

However, if your UI contains several images, it’s better to give them meaningful names:

Image playerIcon = root.Q<Image>("playerIcon");

This makes your code easier to understand and maintain.


6. Accessing Multiple Elements With Query()

Sometimes you don’t want one element.

You want multiple elements of the same type.

That’s where Query<T>() is useful.

For example:

var buttons = root.Query<Button>().ToList();

You can then loop through them:

foreach (var button in buttons)
{
    button.clicked += () =>
    {
        Debug.Log("A button was clicked!");
    };
}

You may need:

using System.Linq;

for ToList().


7. Querying by USS Class

UI Toolkit also allows you to organize elements using USS classes.

For example:

<ui:Button
    name="playButton"
    class="menuButton"
    text="Play" />

Another button could use the same class:

<ui:Button
    name="settingsButton"
    class="menuButton"
    text="Settings" />

Now both buttons belong to:

menuButton

You can query them together:

var buttons = root.Query<Button>(className: "menuButton").ToList();

Then:

foreach (var button in buttons)
{
    button.clicked += () =>
    {
        Debug.Log("Menu button clicked!");
    };
}

This is useful when several UI elements should share behavior.


8. Using USS for Styling

UXML defines the structure.

USS defines the appearance.

For example:

.menuButton {
    width: 200px;
    height: 60px;
}

Your architecture becomes:

UXML
 ↓
Structure

USS
 ↓
Appearance

C#
 ↓
Behavior

This separation is one of the biggest advantages of UI Toolkit.


9. Finding Elements by Name

Names are especially useful for important UI elements.

For example:

<ui:Label name="scoreLabel" />

Then:

Label scoreLabel = root.Q<Label>("scoreLabel");

You can update it during gameplay:

scoreLabel.text = "Score: " + score;

For a game, you might have:

Game UI
│
├── scoreLabel
├── levelLabel
├── timerLabel
├── pauseButton
├── restartButton
└── gameOverPanel

Then retrieve them in your script:

scoreLabel = root.Q<Label>("scoreLabel");
levelLabel = root.Q<Label>("levelLabel");
timerLabel = root.Q<Label>("timerLabel");

pauseButton = root.Q<Button>("pauseButton");
restartButton = root.Q<Button>("restartButton");

gameOverPanel = root.Q<VisualElement>("gameOverPanel");

10. Registering Button Events

Once you’ve retrieved a button, you can register an event.

Button playButton = root.Q<Button>("playButton");

playButton.clicked += StartGame;

Then create the method:

private void StartGame()
{
    Debug.Log("Game Started!");
}

This is cleaner than putting large amounts of UI logic directly inside the initialization method.


11. Removing Button Events

If a UI object can be initialized multiple times, it’s important to consider event subscriptions.

For example:

button.clicked += StartGame;

If you repeatedly register the same callback without removing it, the callback can potentially execute multiple times.

You can remove it with:

button.clicked -= StartGame;

A common pattern is:

private void OnEnable()
{
    button.clicked += StartGame;
}

private void OnDisable()
{
    button.clicked -= StartGame;
}

This is particularly useful for UI that is repeatedly enabled and disabled.


12. Checking for Missing Elements

During development, it’s useful to make sure your UI elements actually exist.

For example:

Button button = root.Q<Button>("myButton");

if (button == null)
{
    Debug.LogError("myButton was not found!");
    return;
}

This can save time when debugging UXML naming mistakes.


13. Recommended Naming Convention

For larger projects, consistent naming helps enormously.

For example:

btnPlay
btnSettings
btnQuit

lblScore
lblLevel
lblTimer

imgPlayer
imgIcon

panelPause
panelGameOver
panelSettings

txtPlayerName

Then your C# code becomes easy to read:

Button btnPlay;
Label lblScore;
VisualElement panelGameOver;

You don’t have to use this exact convention. The important thing is to choose one and use it consistently.


14. A Complete Example

Here’s a simple game UI example:

using UnityEngine;
using UnityEngine.UIElements;

public class GameUI : MonoBehaviour
{
    private UIDocument uiDocument;

    private Label scoreLabel;
    private Label levelLabel;

    private Button playButton;
    private Button pauseButton;

    private VisualElement gameOverPanel;

    private void OnEnable()
    {
        uiDocument = GetComponent<UIDocument>();

        VisualElement root = uiDocument.rootVisualElement;

        scoreLabel = root.Q<Label>("scoreLabel");
        levelLabel = root.Q<Label>("levelLabel");

        playButton = root.Q<Button>("playButton");
        pauseButton = root.Q<Button>("pauseButton");

        gameOverPanel = root.Q<VisualElement>("gameOverPanel");

        playButton.clicked += OnPlayClicked;
        pauseButton.clicked += OnPauseClicked;

        scoreLabel.text = "Score: 0";
        levelLabel.text = "Level 1";

        gameOverPanel.style.display = DisplayStyle.None;
    }

    private void OnDisable()
    {
        playButton.clicked -= OnPlayClicked;
        pauseButton.clicked -= OnPauseClicked;
    }

    private void OnPlayClicked()
    {
        Debug.Log("Play clicked");
    }

    private void OnPauseClicked()
    {
        Debug.Log("Pause clicked");
    }

    public void UpdateScore(int score)
    {
        scoreLabel.text = $"Score: {score}";
    }
}

This provides a clean separation:

UXML
 ↓
Defines UI

C#
 ↓
Finds UI

C#
 ↓
Controls behavior

USS
 ↓
Controls appearance

15. A Common Mistake: Loading UXML Twice

If you’re using a UIDocument component with a UXML assigned to its Source Asset, you generally don’t need to manually call:

uxmlAsset.CloneTree();
root.Add(uxml);

The UIDocument already loads its assigned UXML hierarchy.

In that situation, simply use:

var root = GetComponent<UIDocument>().rootVisualElement;

and query the elements:

var button = root.Q<Button>("myButton");

Manual CloneTree() is useful when you intentionally want to instantiate an additional VisualTreeAsset into another container.

This distinction is important because loading the same UXML twice can result in duplicate UI elements.


Best Practices

When working with Unity UI Toolkit, keep these principles in mind:

Use UXML for structure

What elements exist?

Use USS for styling

How should they look?

Use C# for behavior

What should they do?

Give important elements meaningful names

root.Q<Button>("playButton");

Use Query() when dealing with collections

root.Query<Button>();

Avoid repeatedly cloning the same UXML

Let UIDocument manage its assigned Source Asset unless you intentionally need additional instances.


Conclusion

Unity UI Toolkit provides a powerful system for building modern game interfaces.

The basic workflow is straightforward:

Create UXML
    ↓
Attach UIDocument
    ↓
Get rootVisualElement
    ↓
Q<T>() / Query<T>()
    ↓
Register Events
    ↓
Control UI From C#

Once you understand UIDocument, rootVisualElement, Q(), Query(), UXML, and USS, you can build everything from simple menus to complete game interfaces using a clean separation between structure, styling, and behavior.

For Unity developers, learning UI Toolkit is especially valuable for building scalable interfaces that can be reused across multiple games and projects.

Leave a Comment