Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Roblox/roact/llms.txt

Use this file to discover all available pages before exploring further.

Roact’s reconciliation system is powerful, but it is not always the right tool. Re-rendering an entire component tree to update one animated property every frame would be far too expensive, and some Roblox Instance APIs simply need to be called directly rather than driven by props. For these cases Roact provides two complementary escape hatches: bindings for pushing values directly onto properties, and refs for obtaining a handle to the underlying Roblox Instance itself.

Bindings

Bindings are special objects that Roact knows how to unwrap. When you supply a binding as the value of a prop, Roact subscribes that property to the binding. Whenever the binding’s value changes, Roact updates only that specific property — no reconciliation pass, no component re-render. This makes bindings ideal for animations, dynamically resizing elements, gamepad selection state, and any other situation where you need frequent, targeted updates.

Creating a Binding

Roact.createBinding was added in Roact 1.0.0.
Call Roact.createBinding(initialValue). It returns two values: the binding object itself, and an updater function you call to push a new value into the binding.
Roact.createBinding(initialValue) -> (binding, updateFunction)
Store both in a persistent location such as self inside a stateful component’s init:
local Foo = Roact.Component:extend("Foo")

function Foo:init()
    -- createBinding takes an initial value; 0 is a sensible default for a counter.
    self.clickCount, self.updateClickCount = Roact.createBinding(0)
end

Using a Binding as a Prop

Pass the binding directly where you would normally pass a static value. Roact unwraps it and subscribes the property to future updates:
function Foo:render()
    return Roact.createElement("TextButton", {
        -- Roact unwraps the binding, sets Text to the current value,
        -- and re-sets it automatically whenever the binding changes.
        Text = self.clickCount,

        [Roact.Event.Activated] = function()
            -- Increment the count. Roact will update the Text property directly
            -- without triggering a full re-render of the component.
            self.updateClickCount(self.clickCount:getValue() + 1)
        end,
    })
end
The result is a TextButton that displays the click count. The Text property is updated in-place each time updateClickCount is called — no setState, no reconciliation.

binding:getValue()

Returns the binding’s current value synchronously.
Avoid calling binding:getValue() during render. Reading a binding’s value while rendering bypasses Roact’s subscription mechanism — the component will not automatically re-render when the binding updates, so the displayed value can become stale. Use it only in event handlers, lifecycle methods, and other callbacks that run outside the render path.

Mapped Bindings

Often the raw binding value needs to be transformed before it can be used as an Instance property. binding:map(fn) returns a new, derived binding whose value is the result of applying fn to the upstream binding’s current value. The mapped binding updates automatically whenever the upstream does.
function Foo:render()
    return Roact.createElement("TextButton", {
        -- The mapped binding transforms the number into a readable string.
        Text = self.clickCount:map(function(value)
            return "Clicks: " .. tostring(value)
        end),

        [Roact.Event.Activated] = function()
            self.updateClickCount(self.clickCount:getValue() + 1)
        end,
    })
end
Now the button displays "Clicks: 0" initially, and updates to "Clicks: 1", "Clicks: 2", and so on without any component re-render.
Bindings created by binding:map(fn) are read-only. Calling updateFunction on a mapped binding will throw an error. Only the original binding returned by Roact.createBinding can be updated directly.

Joining Multiple Bindings

Roact.joinBindings was added in Roact 1.1.0.
When a single property depends on more than one binding, use Roact.joinBindings(bindings). Pass it a table of bindings keyed by name; it returns a single binding whose value is a table of the same shape with each binding’s current value. Chain :map on the result to compute a final property value.
Roact.joinBindings(bindings: table) -> binding
For example, suppose you want to size a Frame to be the sum of two other frames’ sizes:
local combinedSize = Roact.joinBindings({
    left  = self.leftFrameSize,
    right = self.rightFrameSize,
}):map(function(sizes)
    -- sizes = { left = UDim2, right = UDim2 }
    return UDim2.new(
        sizes.left.X.Scale  + sizes.right.X.Scale,
        sizes.left.X.Offset + sizes.right.X.Offset,
        sizes.left.Y.Scale  + sizes.right.Y.Scale,
        sizes.left.Y.Offset + sizes.right.Y.Offset
    )
end)

-- combinedSize can now be used as any other binding:
Roact.createElement("Frame", {
    Size = combinedSize,
})
joinBindings re-fires whenever any of the upstream bindings changes, giving the :map function an up-to-date snapshot of all values.

Refs

While bindings target individual properties, refs give you access to the entire Roblox Instance that Roact created. A ref is a special binding that Roact automatically points at the underlying Instance when the component mounts and clears when it unmounts. Refs can only be attached to host components (strings like "TextBox" or "Frame"). They cannot be attached directly to stateful or function components.

Creating and Using a Ref

1

Create the ref in init

Call Roact.createRef() and store the result on self. Because function components recreate their locals on every call, refs must live inside stateful components.
local Foo = Roact.Component:extend("Foo")

function Foo:init()
    self.textBoxRef = Roact.createRef()
end
2

Attach the ref to a host component

Use the special Roact.Ref key when creating the host element:
function Foo:render()
    return Roact.createElement("TextBox", {
        [Roact.Ref] = self.textBoxRef,
    })
end
3

Read the Instance in a lifecycle method

After the component mounts, call ref:getValue() to retrieve the live Roblox Instance:
function Foo:didMount()
    local textBox = self.textBoxRef:getValue()
    print("TextBox has this text:", textBox.Text)
end
Refs expose a current field that maps to the same value as ref:getValue(). This field is deprecated and exists only for backwards compatibility. Prefer ref:getValue() in new code. The current field is also read-only — assigning to it will throw an error.

Refs as Host Properties

Some Roblox Instance properties accept another Instance as their value — NextSelectionLeft, NextSelectionRight, NextSelectionUp, and NextSelectionDown are common examples for gamepad navigation. Because refs are bindings under the hood, Roact’s renderer knows to unwrap them automatically when they appear as property values:
local Bar = Roact.Component:extend("Bar")

function Bar:init()
    self.leftButtonRef  = Roact.createRef()
    self.rightButtonRef = Roact.createRef()
end

function Bar:render()
    return Roact.createElement("Frame", nil, {
        LeftButton = Roact.createElement("TextButton", {
            [Roact.Ref]          = self.leftButtonRef,
            NextSelectionRight   = self.rightButtonRef,
        }),
        RightButton = Roact.createElement("TextButton", {
            [Roact.Ref]         = self.rightButtonRef,
            NextSelectionLeft   = self.leftButtonRef,
        }),
    })
end
Roact resolves the ref to the actual Instance before setting the property, and re-resolves it automatically if the ref ever changes. You do not need to worry about ordering — Roact handles the dependency correctly.

Ref Forwarding

Roact.forwardRef was added in Roact 1.4.0.
Because refs cannot be assigned to function or stateful components directly, a wrapper component that wants to expose its underlying host element must explicitly forward the ref. Wrap the component function with Roact.forwardRef:
Roact.forwardRef(function(props, ref) -> element) -> component
forwardRef strips the Roact.Ref key out of the props table and passes it as a separate second argument to your render function. You can then pass it straight through to a host element. Before forwarding — the ref is silently ignored:
local function FancyTextBox(props)
    return Roact.createElement("TextBox", {
        Multiline        = true,
        PlaceholderText  = "Enter your text here",
        PlaceholderColor3 = Color3.new(0.4, 0.4, 0.4),
        [Roact.Change.Text] = props.onTextChange,
        -- Any [Roact.Ref] passed by the caller never reaches this host element.
    })
end
After forwarding — the ref is wired through to the host TextBox:
local FancyTextBox = Roact.forwardRef(function(props, ref)
    return Roact.createElement("TextBox", {
        Multiline         = true,
        PlaceholderText   = "Enter your text here",
        PlaceholderColor3 = Color3.new(0.4, 0.4, 0.4),
        [Roact.Change.Text] = props.onTextChange,
        [Roact.Ref]         = ref,
    })
end)
A parent component can now do:
local Form = Roact.Component:extend("Form")

function Form:init()
    self.textBoxRef = Roact.createRef()
end

function Form:render()
    return Roact.createElement(FancyTextBox, {
        onTextChange = function(value)
            print("text value updated to:", value)
        end,
        [Roact.Ref] = self.textBoxRef,
    })
end

function Form:didMount()
    -- self.textBoxRef now points to the underlying TextBox Instance.
    self.textBoxRef:getValue():CaptureFocus()
end

Function Refs (Legacy)

The original ref API accepted a plain function as the Roact.Ref prop value rather than a ref object. This style still works but is not recommended for new code.
local function Baz(props)
    return Roact.createElement("TextBox", {
        [Roact.Ref] = function(instance)
            -- Guard against nil: the function is called with nil when
            -- the component unmounts or the ref value changes.
            if instance ~= nil then
                print("TextBox has this text:", instance.Text)
            else
                print("TextBox ref removed.")
            end
        end,
    })
end
Function refs cannot be used as the value of host properties such as NextSelectionLeft. Passing a function where Roblox expects an Instance will produce an error. Use object refs (created with Roact.createRef) for that pattern.
When a function ref is called, there is no guarantee that sibling or parent components have finished mounting. Triggering side effects that depend on those components from inside a function ref callback can produce difficult-to-trace bugs. Additionally, function refs receive nil when the component unmounts or the ref value changes — always guard against this.

Build docs developers (and LLMs) love