UIReferences: A Simple Way to Access UI Elements Without Traversing the Entire Hierarchy

Hello everyone in the community!

Today I want to share a small system I created that has been helping me a lot during development. I called it UIReferences.

Basically, it is a script where you simply add a tag to any element in your interface, whether it is a Frame, Button, TextLabel, or any other UI object, and the system will automatically return the reference to that element inside the player’s GUI.

This removes the need to manually traverse the entire UI hierarchy just to find a specific element. Besides making the code cleaner and easier to maintain, it also makes UI access much more practical.

For example:

Without UIReferences:

local playerGui = player:WaitForChild("PlayerGui")

local screenGui = playerGui:WaitForChild("MainUI")
local hudFrame = screenGui:WaitForChild("HUD")
local coinsFrame = hudFrame:WaitForChild("CoinsFrame")
local coinsTextLabel = coinsFrame:WaitForChild("CoinsTextLabel")

coinsTextLabel.Text = "999"

With UIReferences:

In Studio, simply add the COINS_TEXT_LABEL tag to the CoinsTextLabel TextLabel.

local UIReferences = require(Players.LocalPlayer.PlayerScripts.Util.UIReferences)

local coinsTextLabel = UIReferences:GetReference("COINS_TEXT_LABEL")

coinsTextLabel.Text = "999"

Much simpler, cleaner, and easier to maintain. You no longer need to rely on the UI hierarchy structure to access specific elements.

Important:

  • Place the script inside PlayerScripts.
  • Each tag must be unique. If duplicated tags are found, the system will warn/error about duplicated references.
Code: UIReferences
local UIReferences = {}
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer

function UIReferences:WaitForDescendants(root, ...)
	local names = { ... }
	local current = root

	for _, name in ipairs(names) do
		current = current:WaitForChild(name)

		while not current do
			current = current:WaitForChild(name)
		end
	end

	return current
end

function UIReferences:GetReference(referenceName: string)
	local playerGui = player:WaitForChild("PlayerGui")

	UIReferences:WaitForDescendants(player, "PlayerGui")

	local taggedObjects = CollectionService:GetTagged(referenceName)

	if #taggedObjects > 2 then
		warn("More than one object found with the tag:", referenceName)
		return
	end

	for _, obj in ipairs(taggedObjects) do
		if obj:IsDescendantOf(playerGui) then
			return obj
		end
	end


	local thread = coroutine.running()
	local connection

	connection = CollectionService:GetInstanceAddedSignal(referenceName):Connect(function(obj)
		if obj:IsDescendantOf(playerGui) then

			connection:Disconnect()
			coroutine.resume(thread, obj)
		end
	end)

	return coroutine.yield()
end

return UIReferences

3 Likes

Being obnoxious for the sake of it

I asked why would i use this and it got flagged as “Off-Topic” :sob:, anyways, why would i use this over normal referencing when this seems to be so much work.

Yeah no sorry. This just seems like a ton more effort for a little less code. I don’t see this being useful at all, especially considering you have to go and manually tag everything to then also not have auto-complete in the code when trying to write out tag names which causes you to go back and forth to remind yourself what each individual tag is.
This system is not how you should use collection service.

3 Likes

Cool idea.

Not sure why you are getting so much hate. Freely contributing to other developers is always nice to see.

I’ve certainly worked with UI that has become messy and I do end up having quite a bit of clutter just trying to reference what I need.

What would be cool is if you made a version of this that given a parent, e.g. screengui, creates a dictionary of all the objects it finds. That way its even less clutter.

Could even just have it do by unique names, like assuming you name your UI relatively cleanly, e.g. only one ExitButton, then the UIReferences could just automatically be those names, and things like TextLabel or Frame, stuff that you haven’t named could be ignored since if you didn’t bother to name it you probably aren’t planning on using it anyway.

So instead of having to do

local coinsTextLabel = UIReferences:GetReference("COINS_TEXT_LABEL")
local coinsTextLabel2 = UIReferences:GetReference("COINS_TEXT_LABEL2")
local coinsTextLabel3 = UIReferences:GetReference("COINS_TEXT_LABEL3")
local coinsTextLabel4 = UIReferences:GetReference("COINS_TEXT_LABEL4")
local coinsTextLabel5 = UIReferences:GetReference("COINS_TEXT_LABEL5")
local coinsTextLabel6 = UIReferences:GetReference("COINS_TEXT_LABEL6")

You could have something like this

local myParent = ... -- e.g. a screenGui
local ui = UIReferences:GetReferences(myParent) -- dictionary

ui.coinsTextLabel4.Text = "Cheese"

This is just an idea I had as I was reading your post so it definitely could be improved upon.

you are on right track to invent QueryDescendants

1 Like

Think this is a bit different?