rootVisualElement is null in build but not in Editor

I made the mistake of rewriting my menu to use UIElements, and now I can’t play my game.

The problem is in the title. I have a menu built as a UIDocument. It works fine in editor mode, mostly.

But in play mode the rootVisualElement is null and my script failed.

What can I do?

There is one other problem in editor mode. I add a style class to an object whose only attribute is to set the visibility to hidden. But when I add the class the object is not hidden, and the visibility attribute of the node returns null. I can set it manually and it works but the class does not for some reason. But at least I can get around this problem. I can’t get the build to play at all.

I just had the idea to move my initialization code from Awake to Start and it works in the build now.

Still no idea why my style class does not work though.

If this is something you can reproduce then please file a bug report so we can look into it. The editor and player should have the same behavior.

This is not the first time I have run into such issues. I have seen it with systems other than UIElements, where the Awake cycle timing w.r.t. other systems is different between editor and build. Some things that are already initialized during Awake from within the editor are not yet initialized in Awake within the build.

That’s why the idea crossed my mind to try it out.

You can manually set the order scripts are executed in, this may help you keep the same behavior https://docs.unity3d.com/Manual/class-MonoManager.html

Have exact same issue in 2025 using unity 6000.0.38f1. Using any of UI Toolkit was a mistake for sure, just never do this.

I mean its general principle in Unity that any anything that needs to initialize based on the readiness of something else should be done in Start/OnEnable. UIDocument readies itself in Awake, so anything that depends on it should run in OnEnable/Start. This is general good-practice.

How do you know that it’s in Awake()?
Many docs examples use rootVisualElement in Awake() already, so it must be ready earlier than Awake(). However, for runtime-activated UIDocument it seems to be later, so it doesn’t seem consistent.

Here’s what I’m observing based on tests:

  1. For GameObjects enabled from the beginning that have UIDocument + MyScript I can access rootVisualElement in Awake() of MyScript consistently.
  2. For GameObjects where UIDocument is enabled at runtime (see below) I can’t access rootVisualElement in Awake() of MyScript, but it seems to work already in OnEnable() and in Start().

So, my main question is: where is the first place it is guaranteed to be ready if UIDocument is enabled at runtime?

And second question: Does enabling components in MyActivator in order guarantee their Awake()s are also called in order of activation?

// MyActivator.cs
class MyActivator : MonoBehaviour {
	private void Init() {
		GetComponent<UIDocument>().enabled = true;
		GetComponent<MyScript>().enabled = true;
	}
}

// MyScript.cs
class MyScript : MonoBehaviour {
	private void Awake() {
		// This results in NullReferenceException for me when UIDocument is activated at runtime,
		// but it works when it's been active from the beginning.
		rootVisualElement...
	}

	private void OnEnable() {
		rootVisualElement... // This works, but is it safe to access here?
	}

	private void Start() {
		rootVisualElement... // This works, but is it safe to access here?
	}
}

Looking at the source code it looks to be kinda both? UnityCsReference/Modules/UIElements/Core/GameObjects/UIDocument.cs at master · Unity-Technologies/UnityCsReference · GitHub

More accurately it seems to rebuild the tree in OnEnable, so if you hook into anything on Awake you’ll likely be registering to an ‘old’ version of the visual tree.

In either case, you still want to register any callbacks, etc, in OnEnable or later, as UIDocument sets a -100 execution order.

I’ve always done it on OnEnable without issue.

You’re right, I misread the documentation, the documentation uses OnEnable everywhere but I read it as Awake for some reason. I don’t know how it could have happened.

These pages mention the initialization of UXML happens in OnEnable:

And the code you linked to confirms forced -100 execution order (it’s not reflected in [Project Settings > Script Execution Order], but it’s -100).

So, the only safe place to start interacting with UXML is in OnEnable. It works with Awake only accidentally if UIDocument component has been active since startup. Activation at runtime requires using OnEnable in other components to interact with UIDocument.