UI
A small declarative builder for instance trees.
The kit does not ship a dialog, but it does ship the thing you need to write one. This is that thing: enough structure to describe a tree in one expression, with none of the reactivity a real framework brings, so it never competes with the Fusion, React or Vide your game may already use.
const ui = UI.scoped(context.Janitor)
const card: Frame = ui("Frame", {
Name = "TutorialCard",
Size = UDim2.fromOffset(420, 160),
BackgroundColor3 = Color3.fromRGB(18, 18, 22),
[UI.Children] = {
ui("UICorner", { CornerRadius = UDim.new(0, 10) }),
ui("TextButton", {
Text = "Continue",
[UI.Event("Activated")] = function()
print("clicked")
end,
}),
},
Parent = screenGui,
})
Three things a bare Instance.new does not do
Everything it creates, instances and connections alike, goes on the Janitor you scoped it to. A renderer written this way cannot outlive its step.
Parent is applied last, after properties and children. Parenting first makes the engine
recompute layout on every following assignment; Fusion skips Parent in its property loop for
the same reason.
A property the class does not have raises an error naming the class and the property, at construction, instead of failing silently.
On the return type
ui(...) returns any, so annotate the variable with the class you asked for and everything
downstream of it is typed. Typing the property table per class needs generated types, which is
why Fusion's New also gives up and returns a plain Instance. The property check above is
what catches mistakes instead.
Types
KeyKind
type KeyKind = "Children" | "Event" | "Changed"Key
A special key in a property table, produced by UI.Children, UI.Event or UI.Changed.
Props
A property table: class properties by name, plus the special keys.
Create
What UI.scoped returns. Annotate the result with the class you asked for.
Properties
Children
This item is read only and cannot be modified. Read OnlyUI.Children: KeyMarks the list of children in a property table.
ui("Frame", {
[UI.Children] = {
ui("UIListLayout", { Padding = UDim.new(0, 6) }),
ui("TextLabel", { Text = "Hello" }),
},
})
Children are parented before the frame itself is, which is the ordering that avoids a layout pass per property.
Functions
Event
UI.Event(eventName: string--
Name of the event on the class
) → Key--
Use it as a key in a property table
Connects a handler to an event.
ui("TextButton", {
Text = "Continue",
[UI.Event("Activated")] = function()
request.Choices[1].Activate()
end,
})
The connection goes on the scoped Janitor, so you never disconnect it by hand. An event the class does not have raises an error at construction.
Changed
Connects a handler to GetPropertyChangedSignal.
ui("Frame", {
[UI.Changed("AbsoluteSize")] = function()
relayoutCaption()
end,
})
scoped
constructorUI.scoped(janitor: Janitor--
Owns everything created through the returned function
) → Create--
Call it as ui("ClassName", props)
Binds a Janitor and returns the creator.
Inside an action, the Janitor to use is the step's:
Custom.new(function(context)
const ui = UI.scoped(context.Janitor)
const caption: TextLabel = ui("TextLabel", {
Text = "Press the glowing button",
Size = UDim2.fromOffset(320, 40),
BackgroundTransparency = 1,
Parent = context.Spotlight:GetOverlay(),
})
end)
When the step ends, the caption and every connection made through ui go with it. In a dialog
renderer, make your own Janitor and destroy it from DialogHandle.