2025 update: Roact has been superseded by react-lua
React-lua is a replacement for Roact that offers a much closer experience to mainline React (functional components, hooks, etc). It is developed and used internally by Roblox and has a community-maintained fork. Roact is no longer maintained and should not be used for new work.
React-lua Links
- (Recommended) Community Maintained Fork: https://github.com/jsdotlua/react-lua
- Staff-Authored Tutorial: https://devforum.roblox.com/t/how-to-react-roblox/2964543
Now onto the original articleā¦
Video Version
Absurdly Long Text Version
If youāre looking for an advanced way to create your user interfaces, Roact might be the tool for you. Today Iāll be going over what it is, why you might want to use it, and how to use it.
Contents
- What is Roact?
- Why Roact?
- Elements
- Components
- Function Components
- Stateful Components
- Props
- Events
- Change Events
- Bindings
- Mappings
- State
- Refs
- Portals
- Fragments
- Context
- Callbacks
- Conclusion
What is Roact?
Roact is a declarative Lua UI library similar to Facebookās React. Yeah, I stole that opening phrase from the documentation, what of it?
Essentially, by using Roact, you program UI elements from the ground up as opposed to fabricating them in StarterGui beforehand. A typical project includes building a tree of modules, each with their own instructions on how to display its particular component. This information is then passed to the Roact library, which mounts it on the PlayerGui and does its best to reconcile what the player sees with what youāve described.
Why Roact?
Why would I go through the effort of programming all the properties of my interface line-by-line and learning this complicated framework when I have a perfectly functional graphical editor built into Studio? Thatās essentially what my opinion of Roact was before I started using it for myself. However, as you work on bigger and more complicated projects, the perks of Roact begin to reveal themselves.
The core function of Roact itself is taking a description of what you want and drawing it on the screen. Describing what you want can take a long time, yes, but the rest of the development process can be dramatically simplified thanks to the declarative nature of Roact. Thatās the second time Iāve used that word I donāt understand, so letās define that. The concept of declarative programming is centered around describing what the program should do, as opposed to how to do it ā the how is what Roact manages.
There are other neat perks to Roact, such as the reusable nature of the individual components ā not just their appearance, but their functionality as well. Having your interface based entirely on code as opposed to objects also makes cooperating with other developers through Git and Rojo a hundred times better, since the whole team doesnāt constantly have to make sure theyāre using the latest set of UI elements lest everything break.
Elements
Okay, Iāve done my best to explain these fairly abstract concepts, but you can only learn so much before applying anything, so letās get into an example.
First, weāll make an element with the createElement function of the Roact library, which weāll assume weāve placed into ReplicatedStorage:
local Roact = require(game.ReplicatedStorage.Roact)
local interface = Roact.createElement("ScreenGui",{
Name = "Interface";
})
This call creates a ScreenGui and sets its Name property to āInterfaceā, since the first argument is the type of element we want to create and the second argument is a dictionary of properties, or props. āElementā is the term used to refer to an individual object created by Roact.
We can also add a third parameter in the form of a dictionary or table to assign descendants to the element. Letās add a TextLabel with the text āHello, Roact!ā inside of the ScreenGui. To give a specific name to a child element, weāll need to set the elementās key in the dictionary of children equal to whatever we want its name to be.
local Roact = require(game.ReplicatedStorage.Roact)
local interface = Roact.createElement("ScreenGui",{
Name = "Interface";
},{
Label = Roact.createElement("TextLabel",{
Text = "Hello, Roact!";
Size = UDim2.new(0,200,0,50);
})
})
Finally, we can load our interface into the game through the mount function, passing through the complete element tree and the place we want to mount it. This simple example should give you a good idea of how to describe what you want and display it through Roact.
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local interface = Roact.createElement("ScreenGui",{
Name = "Interface";
},{
Label = Roact.createElement("TextLabel",{
Text = "Hello, Roact!";
Size = UDim2.new(0,200,0,50);
})
})
Roact.mount(interface,playerGui)

Components
However, describing individual elements in a tree is just the tip of the iceberg, and the true advantages of Roact begin to reveal themselves with components. Components take many forms, but they are best considered as different classes of elements that support custom behavior. The simplest component is a host component, which is simply a standard Roblox ClassName. We used these in our previous example, where ScreenGui and TextLabel were our components.
Function Components
The next type of component is a function component, which has the ability to be defined by the developer. A function component is simply a function that receives a dictionary of props, which can be used in any manner, and then returns any objects to add to the tree.
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
function Interface(props)
return Roact.createElement("ScreenGui",{},{
Label = Roact.createElement("TextLabel",props,props[Roact.Children])
})
end
local interface = Roact.createElement(Interface,{
Size = UDim2.new(0,200,0,50);
Text = "Hello, Roact!";
})
Roact.mount(interface,playerGui)

This example accepts a list of props and then assigns them to the TextLabel. Itās worth noting that, for custom components, itās up to you to embed its children properly. You can access a componentās children through the props dictionary using the Roact.Children key.
Stateful Components
The most powerful component is the stateful component. Theyāre more advanced, but allow for significantly more functionality, with support for powerful features like state, which weāll discuss later. For now, letās focus on the lifecycle methods by stepping through a simple example.
First, we need to create our component by extending the Component class included with Roact. Letās call it āGreetingā and pass the name as an argument to the extend function for debugging purposes.
local Roact = require(game.ReplicatedStorage.Roact)
local Greeting = Roact.Component:extend("Greeting")
Next, weāll make the render method, which is the heart of the component. This method uses the props provided in the objectās state to provide elements to add to the tree. Letās create a TextLabel, using the name prop weāll pass to the component. This component can now be used to create a TextLabel with the provided name:
local Roact = require(game.ReplicatedStorage.Roact)
local Greeting = Roact.Component:extend("Greeting")
function Greeting:render()
return Roact.createElement("ScreenGui",{
Name = "Interface";
},{
[self.props.name] = Roact.createElement("TextLabel",{
Size = UDim2.new(0,200,0,50);
})
})
end
For its extensive functionality and fairly simple implementation, weāll be using stateful components for the rest of the examples.
Itās typical for individual components to be placed into their own ModuleScripts for ease of organization, where each module just returns the component:
Greeting.lua
local Roact = require(game.ReplicatedStorage.Roact)
local Greeting = Roact.Component:extend("Greeting")
function Greeting:render()
return Roact.createElement("ScreenGui",{
Name = "Interface";
},{
[self.props.name] = Roact.createElement("TextLabel",{
Size = UDim2.new(0,200,0,50);
})
})
end
return Greeting
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local Greeting = require(script.Parent:WaitForChild("Greeting"))
local interface = Roact.createElement(Greeting,{
name = "TestName"
})
Roact.mount(interface,playerGui)

Props
Weāve already used props a fair bit, but letās go into more detail. With host components, provided props should only be key-value pairs that correspond to existing properties of the Roblox instance being created. However, when we step into developer-defined components, props simply become a way to send arguments to the component. For instance, you could make the type of element created dependent on a prop, as shown in this example:
RandomComponent.lua
local Roact = require(game.ReplicatedStorage.Roact)
local RandomComponent = Roact.Component:extend("RandomComponent")
function RandomComponent:render()
return Roact.createElement(self.props.className)
end
return RandomComponent
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local RandomComponent = require(script.Parent:WaitForChild("RandomComponent"))
local interface = Roact.createElement(RandomComponent,{
className = "ScreenGui"
})
Roact.mount(interface,playerGui)

Events
Handling events with Roact is incredibly simple. The event and handler are passed as a key-value pair in the props dictionary, just like any normal property. Events should be referenced through the Event dictionary included with Roact. In this case, I can detect when the button is clicked with Roact.Event.Activated:
local Roact = require(game.ReplicatedStorage.Roact)
local Button = Roact.Component:extend("Button")
function Button:render()
return Roact.createElement("TextButton",{
Size = UDim2.new(0,200,0,50);
[Roact.Event.Activated] = function()
end
})
end
Next, Iāll write a function to handle the event. Handlers should expect to receive the actual underlying Roblox instance associated with the event, followed by any parameters typically provided by the event. The Activated event provides an InputObject and a click count, so Iāll include those and print the click count.
Button.lua
local Roact = require(game.ReplicatedStorage.Roact)
local Button = Roact.Component:extend("Button")
function Button:render()
return Roact.createElement("TextButton",{
Size = UDim2.new(0,200,0,50);
[Roact.Event.Activated] = function(object,inputObject,clickCount)
print(clickCount)
end
})
end
return Button
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local Button = require(script.Parent:WaitForChild("Button"))
local interface = Roact.createElement("ScreenGui",{},{
Roact.createElement(Button)
})
Roact.mount(interface,playerGui)

Change Events
Roact also has its own variation of the GetPropertyChangedSignal function to detect changes to specific properties of an instance. Its syntax is essentially identical, except the property name should be used instead of the event name, and it should be used to index the Change dictionary instead of the Event dictionary. The handler will only receive the instance that changed.
TextBox.lua
local Roact = require(game.ReplicatedStorage.Roact)
local TextBox = Roact.Component:extend("TextBox")
function TextBox:render()
return Roact.createElement("TextBox",{
Size = UDim2.new(0,200,0,50);
[Roact.Change.Text] = function(object)
print(object.Text)
end
})
end
return TextBox
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local TextBox = require(script.Parent:WaitForChild("TextBox"))
local interface = Roact.createElement("ScreenGui",{},{
Roact.createElement(TextBox)
})
Roact.mount(interface,playerGui)


Bindings
So, what can we do with event handlers? Well, for one, we can modify the componentās appearance with bindings. We mentioned lifecycle methods before, and this is a case where one of those methods will come in handy. The init method runs when the component is first mounted, which makes it the best place to define things like bindings. Letās do that.
For this example, weāll create a TextButton that displays the click count of the last activation, so weāll want a binding to hold the latest click count. We can create a binding with the createBinding function, providing a default value. In our case, we can just set it to 0. The createBinding function actually provides two results: the binding itself, and a function to execute it. Itās standard practice to place both of these into the component itself, with the binding being given a normal name and the update function being called āupdateā with the name after that.
local Roact = require(game.ReplicatedStorage.Roact)
local Button = Roact.Component:extend("Button")
function Button:init()
self.clickCount, self.updateClickCount = Roact.createBinding(0)
end
In our render method, weāll create a simple TextButton element and set its text equal to the clickCount binding. Now, whenever the clickCount is updated, the UI elementās property will automatically adjust itself to reflect the change. Letās actually update the binding by bringing in our previous event example and calling the updateClickCount function when the button is activated. The update function expects to receive a new value for the binding, and since we want to increment the clickCount, weāll need to get access to the value, not just the binding itself. We can do that with getValue, which returns the raw value without the binding. This means we can add the clickCount used in the activation to the existing clickCount bindingās value and then pass this to the updateClickCount function, automatically updating the buttonās text to reflect the change.
Button.lua
local Roact = require(game.ReplicatedStorage.Roact)
local Button = Roact.Component:extend("Button")
function Button:init()
self.clickCount, self.updateClickCount = Roact.createBinding(0)
end
function Button:render()
return Roact.createElement("TextButton",{
Size = UDim2.new(0,200,0,50);
Text = self.clickCount;
[Roact.Event.Activated] = function(object,inputObject,clickCount)
self.updateClickCount(self.clickCount:getValue()+1)
end
})
end
return Button
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local Button = require(script.Parent:WaitForChild("Button"))
local interface = Roact.createElement("ScreenGui",{},{
Roact.createElement(Button)
})
Roact.mount(interface,playerGui)

Mappings
But what if we want to add some text to the button besides the number? If we convert the raw value to a string and add it to a phrase, the property wonāt update with the binding anymore. We need a way to mutate the binding in a way that doesnāt disconnect it, and that way is creating a map for the binding. Bindings have a method called map that accepts a transforming function. The function itself receives the bindingās latest value and should return the modified value to be applied to the property. Now we can modify our binding before displaying it:
Button.lua
local Roact = require(game.ReplicatedStorage.Roact)
local Button = Roact.Component:extend("Button")
function Button:init()
self.clickCount, self.updateClickCount = Roact.createBinding(0)
end
function Button:render()
return Roact.createElement("TextButton",{
Size = UDim2.new(0,200,0,50);
Text = self.clickCount:map(function(clickCount)
return "Click Count: "..tostring(clickCount)
end);
[Roact.Event.Activated] = function(object,inputObject,clickCount)
self.updateClickCount(self.clickCount:getValue()+1)
end
})
end
return Button
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local Button = require(script.Parent:WaitForChild("Button"))
local interface = Roact.createElement("ScreenGui",{},{
Roact.createElement(Button)
})
Roact.mount(interface,playerGui)

State
State is very similar to bindings, with only a couple differences, the main one being how it re-renders the entire element when itās updated as opposed to just changing any bound props. This comes in handy for situations where your use of a variable proves to be more complex than just āput value here.ā
Letās suppose we have a situation where we want to show a completely different element depending on the current state. Weāll extend a new component and set the default state within the init method using setState, which accepts a dictionary of key-value pairs to write. If a value already exists in the state, itāll be overwritten by the value passed here. If no value is passed, it will retain its previous version. For our purposes, letās just create a flag to track which element should be visible, flipped on by default:
local Roact = require(game.ReplicatedStorage.Roact)
local Button = Roact.Component:extend("Button")
function Button:init()
self:setState({
elementFlag = true;
})
end
Now, in the render method, we can check if the elementFlag value is true. If it is, weāll return a TextButton that can be clicked. If it isnāt, weāll just return a TextLabel with some text telling the user to be patient:
local Roact = require(game.ReplicatedStorage.Roact)
local Button = Roact.Component:extend("Button")
function Button:init()
self:setState({
elementFlag = true;
})
end
function Button:render()
if self.state.elementFlag then
return Roact.createElement("TextButton",{
Size = UDim2.new(0,200,0,50);
Text = "Click me!";
})
else
return Roact.createElement("TextLabel",{
Size = UDim2.new(0,200,0,50);
Text = "Please wait...";
})
end
end
Letās detect when the button is clicked and update the state to disable the flag. Then weāll wait 2 seconds and flip it back. This will give us a TextButton that changes to a TextLabel for 2 seconds when we click on it.
Button.lua
local Roact = require(game.ReplicatedStorage.Roact)
local Button = Roact.Component:extend("Button")
function Button:init()
self:setState({
elementFlag = true;
})
end
function Button:render()
if self.state.elementFlag then
return Roact.createElement("TextButton",{
Size = UDim2.new(0,200,0,50);
Text = "Click me!";
[Roact.Event.Activated] = function()
self:setState({
elementFlag = false;
})
wait(2)
self:setState({
elementFlag = true;
})
end
})
else
return Roact.createElement("TextLabel",{
Size = UDim2.new(0,200,0,50);
Text = "Please wait...";
})
end
end
return Button
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local Button = require(script.Parent:WaitForChild("Button"))
local interface = Roact.createElement("ScreenGui",{},{
Roact.createElement(Button)
})
Roact.mount(interface,playerGui)


Refs
Sometimes itās necessary to gain access to an elementās underlying Roblox instance, typically when assigning properties to other objects that expect instances as their properties. Refs are ideal for this. Theyāre very similar to bindings, both in terms of syntax and usage.
Letās suppose we want to create a SurfaceGui that appears on a part we add to workspace. To do this, weāll need to set the SurfaceGuiās Adornee property to the part, which means we need to create a ref pointing to the part in workspace. Weāll start by using the createRef function to define our ref in the init method. The createRef function doesnāt accept any arguments.
local Roact = require(game.ReplicatedStorage.Roact)
local SurfaceGui = Roact.Component:extend("SurfaceGui")
function SurfaceGui:init()
self.partRef = Roact.createRef()
end
Next, in the render method, weāll create a Part in workspace. To assign it to our ref, weāll pass a prop through to it with the key of Roact.Ref and the value of the ref we defined in init. Then weāll add a SurfaceGui as a child of the Part, and as we define its props, weāll set its Adornee to the ref.
local Roact = require(game.ReplicatedStorage.Roact)
local SurfaceGui = Roact.Component:extend("SurfaceGui")
function SurfaceGui:init()
self.partRef = Roact.createRef()
end
function SurfaceGui:render()
return Roact.createElement("Part",{
Anchored = true;
Position = Vector3.new(0,10,0);
[Roact.Ref] = self.partRef;
},{
SurfaceGui = Roact.createElement("SurfaceGui",{
Adornee = self.partRef;
},{
TextLabel = Roact.createElement("TextLabel",{
Text = "Hello, Roact!";
Size = UDim2.new(1,0,1,0);
})
})
})
end
Much like bindings, youāll need to call getValue on the ref to get its actual value outside of use in props, though you should keep in mind that itāll return nil until the object is mounted:
--yes I know willMount() does not exist but it's just a demonstration
function SurfaceGui:willMount()
print(self.partRef:getValue()) --"nil"
end
function SurfaceGui:didMount()
print(self.partRef:getValue()) --"Part"
end
SurfaceGui.lua
local Roact = require(game.ReplicatedStorage.Roact)
local SurfaceGui = Roact.Component:extend("SurfaceGui")
function SurfaceGui:init()
self.partRef = Roact.createRef()
end
function SurfaceGui:render()
return Roact.createElement("Part",{
Anchored = true;
Position = Vector3.new(0,10,0);
[Roact.Ref] = self.partRef;
},{
SurfaceGui = Roact.createElement("SurfaceGui",{
Adornee = self.partRef;
},{
TextLabel = Roact.createElement("TextLabel",{
Text = "Hello, Roact!";
Size = UDim2.new(1,0,1,0);
})
})
})
end
return SurfaceGui
Interface.client.lua
local Roact = require(game.ReplicatedStorage.Roact)
local SurfaceGui = require(script.Parent:WaitForChild("SurfaceGui"))
local interface = Roact.createElement(SurfaceGui)
Roact.mount(interface,workspace)

this is impossible to read lol
Portals
Thing is, putting our SurfaceGui inside of the Part isnāt necessarily the best idea. It would make more sense to put it into the PlayerGui instead, but as youāve probably caught on, the tree structure of Roact elements would make that difficult without mounting a completely separate Roact tree⦠and thatās where portals come in.
Portals make it possible to parent Roact elements under other objects outside of the tree. A portal acts as its own element, and the only prop it receives is a target which serves as a new parent for any of the portalās children. Any children added to the portal will appear under the portalās target, so weāll move our SurfaceGui to be under the portal.
SurfaceGui.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local SurfaceGui = Roact.Component:extend("SurfaceGui")
function SurfaceGui:init()
self.partRef = Roact.createRef()
end
function SurfaceGui:render()
return Roact.createElement("Part",{
Anchored = true;
Position = Vector3.new(0,10,0);
[Roact.Ref] = self.partRef;
},{
Roact.createElement(Roact.Portal,{
target = playerGui;
},{
SurfaceGui = Roact.createElement("SurfaceGui",{
Adornee = self.partRef;
},{
TextLabel = Roact.createElement("TextLabel",{
Text = "Hello, Roact!";
Size = UDim2.new(1,0,1,0);
})
})
})
})
end
return SurfaceGui
Interface.client.lua
local Roact = require(game.ReplicatedStorage.Roact)
local SurfaceGui = require(script.Parent:WaitForChild("SurfaceGui"))
local interface = Roact.createElement(SurfaceGui)
Roact.mount(interface,workspace)

Fragments

In this example, we have a Frame with a UIListLayout inside of it, and a component that provides labels to be shown in the frame. However, in order for the labels to be affected properly by the layout, they need to be under the same parent. We can only return one element through the labels componentās render method, though, so how do we find a workaround? Our best bet is to use fragments.
Fragments are useful for creating a component that returns multiple objects without wrapping them in another element. To make a fragment, simply use the createFragment function, which accepts a dictionary of elements as its only argument. When we return the fragment, you can see that it now renders on the same level as the UIListLayout.
Labels.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local Labels = Roact.Component:extend("Labels")
local contents = {
"hello world";
"owo wats this";
}
function Labels:init()
self.partRef = Roact.createRef()
end
function Labels:render()
local labels = {}
for index,content in pairs(contents) do
labels[index] = Roact.createElement("TextLabel",{
Size = UDim2.new(0,200,0,50);
Text = content;
})
end
return Roact.createFragment(labels)
end
return Labels
List.lua
local Roact = require(game.ReplicatedStorage.Roact)
local Labels = require(script.Parent.Labels)
local List = Roact.Component:extend("List")
function List:render()
return Roact.createElement("Frame",{
Size = UDim2.new(0,400,0,200);
},{
UIListLayout = Roact.createElement("UIListLayout");
Labels = Roact.createElement(Labels)
})
end
return List
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local List = require(script.Parent:WaitForChild("List"))
local interface = Roact.createElement("ScreenGui",{},{
Roact.createElement(List)
})
Roact.mount(interface,playerGui)


Context
Previously we talked about passing information down to lower-level components through props, but this gets rather tiresome, particularly when it comes to nesting components. To simplify this process, we can use context.
Context can be thought of as a more complex version of state that spreads across multiple components. The first step is to create a Context object with the createContext function, which accepts a default value as its only parameter. The function returns a new context, which has a Provider and a Consumer class we can use later.
ThemeContext.lua
local Roact = require(game.ReplicatedStorage.Roact)
local ThemeContext = Roact.createContext({
backgroundColor = Color3.new(0.3,0.3,0.3);
textColor = Color3.new(1,1,1);
})
return ThemeContext
Later, in the component that needs access to the context information, weāll wrap the contents of the render method into a new element, passing the Consumer of the context through as the ElementKind. The only prop a Consumer component requires is a render function that receives the context information and returns the rest of the component like normal.
Button.lua
local Roact = require(game.ReplicatedStorage.Roact)
local ThemeContext = require(script.Parent:WaitForChild("ThemeContext"))
local Button = Roact.Component:extend("Button")
function Button:render()
return Roact.createElement(ThemeContext.Consumer,{
render = function(theme)
return Roact.createElement("TextButton",{
Position = self.props.Position;
Size = UDim2.new(0,200,0,50);
BackgroundColor3 = theme.backgroundColor;
TextColor3 = theme.textColor;
})
end
})
end
return Button
In this example, Iām setting the background color and text color of the label equal to the theme settings defined in the default value of my context module. But what if I wanted to change the theme while the game was running, or only for a specific section of the Roact tree? This is where providers come in.
Letās create a new component called SubTheme. In its render method, weāll make a new element using the Provider of our context as the ElementKind. Providers only expect a single prop, which should be the new context value. Weāll also want to pass through the children of the component so theyāll actually render.
SubTheme.lua
local Roact = require(game.ReplicatedStorage.Roact)
local ThemeContext = require(script.Parent:WaitForChild("ThemeContext"))
local SubTheme = Roact.Component:extend("SubTheme")
function SubTheme:render()
return Roact.createElement(ThemeContext.Provider,{
value = {
backgroundColor = Color3.new(1,0.3,0.3);
textColor = Color3.new(1,1,1);
}
},self.props[Roact.Children])
end
return SubTheme
Now, we can surround components we want to affect with the different context with our SubTheme component.
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local Button = require(script.Parent:WaitForChild("Button"))
local SubTheme = require(script.Parent:WaitForChild("SubTheme"))
local interface = Roact.createElement("ScreenGui",{},{
Button1 = Roact.createElement(Button,{
Position = UDim2.new(0,0,0,0);
});
SubTheme = Roact.createElement(SubTheme,{},{
Button2 = Roact.createElement(Button,{
Position = UDim2.new(0,0,0,50);
});
});
})
Roact.mount(interface,playerGui)

Callbacks
Alright, this part isnāt necessarily a feature of Roact, itās more of a method, but itās important to cover because it will make your life a whole lot easier. Everything weāve discussed so far regarding the flow of information between components has revolved around passing info down the tree to sub-components. However, thereāll be a lot of times where youāll want to pass information up the tree as well. Think of a close button component that needs to toggle the parent frameās visibility.
In this scenario, itās best to create your own updater function in the frame component and pass the function down to the button component as a prop. From the button component, you can hook up the buttonās Activated event to the updater function, which will change the frameās visibility when the button is clicked. Heck, in this example, you donāt even need to create your own updater function ā you can just create a binding for the frameās Visible property and pass the bindingās updater function down instead.
This may seem obvious to you, but I only just figured it out and man, itās gonna save me a lot of headache down the line.
Button.lua
local Roact = require(game.ReplicatedStorage.Roact)
local Button = Roact.Component:extend("Button")
function Button:render()
return Roact.createElement("TextButton",{
Size = UDim2.new(0,32,0,32);
Text = "X";
BackgroundColor3 = Color3.new(1,0.3,0.3);
[Roact.Event.Activated] = function()
self.props.callback()
end;
})
end
return Button
Frame.lua
local Roact = require(game.ReplicatedStorage.Roact)
local Button = require(script.Parent:WaitForChild("Button"))
local Frame = Roact.Component:extend("Frame")
function Frame:init()
self.visibility,self.updateVisibility = Roact.createBinding(true)
end
function Frame:render()
return Roact.createElement("Frame",{
Size = UDim2.new(0,400,0,250);
AnchorPoint = Vector2.new(0.5,0.5);
Position = UDim2.new(0.5,0,0.5,0);
Visible = self.visibility;
},{
Close = Roact.createElement(Button,{
callback = function()
self.updateVisibility(false)
end
})
})
end
return Frame
Interface.client.lua
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
local Roact = require(game.ReplicatedStorage.Roact)
local Frame = require(script.Parent:WaitForChild("Frame"))
local interface = Roact.createElement("ScreenGui",{
Name = "Interface";
ResetOnSpawn = false;
},{
Frame = Roact.createElement(Frame)
})
Roact.mount(interface,playerGui)

Conclusion
Thereās no perfect UI framework. Itās all about tradeoffs, and despite some limitations thatāll be annoying at times, I consider the organization Roact provides to be worth the extra time and effort. I suggest you try it as well and see if itās right for you. Talk to your doctor about Roact today.
Now, Roact is really complex, and I didnāt have time to dive deep into every aspect of it. Youāll undoubtedly come across problems that even I donāt know how to solve. In that case, Iād highly suggest you check out a few sources that helped me out tremendously.
The first place to look when youāre stuck is obviously the Roact documentation, which is hosted on Github. The API reference especially does a great job of showing every tool at your disposal, and Iāve basically always got it open on a secondary monitor. Thing is, for questions that are more complex and less āwhat does X do,ā just reading documentation wonāt always get you far. In that case, the Roblox Open Source Community Discord server has a channel specifically for Roact thatās full of some really cool people with encyclopedic knowledge of the program who literally made Roact what it is. You can search for your question in there, because chances are someoneās asked it already. Otherwise, you can just ask it yourself. Links to the docs, Discord server, examples, and additional resources are listed below. This took forever to make, and I am going to go die now. Goodbye.
Resources
Here are some useful resources to learn more about Roact:
Example Files: https://github.com/ChipioIndustries/Roact-Tutorial
Download Roact: https://github.com/Roblox/roact/releases
Roact API Reference: https://roblox.github.io/roact/api-reference/
Roact Documentation: https://roblox.github.io/roact/
RDC18 Roact Talk: https://www.youtube.com/watch?v=vHTXAsHqrPI
Roact UI Animations: https://devforum.roblox.com/t/643494
Roblox Open Source Community Discord: https://discord.gg/85eegJn
