FragmentFragment

Create a Handle

Bind a renderer to a UI Instance and react to state changes.

A Handle is the core unit of Fragment. It resolves a path from PlayerGui down to a specific UI Instance, then re-runs a renderer function whenever its state or subscribed data changes.


Define a Handle

Each Handle lives in its own ModuleScript inside your Handles/ folder.

Handles/ShopHandle.luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Fragment = require(ReplicatedStorage.Fragment)

local Handle = Fragment.newHandle("Shop", { "HUD", "Shop", "MainFrame" })

return Handle(function(element: Frame)
    -- renderer body
end)

newHandle takes two arguments:

  • name: a unique identifier used to retrieve this Handle via Fragment.getHandle
  • path: child names traversed from PlayerGui to reach the target Instance

One Handle per ModuleScript

Each ModuleScript should create and return exactly one Handle. Fragment.load requires every ModuleScript it finds — non-Handle modules like configs or utilities are required and ignored safely.


Local state with bind

Handle:bind creates a reactive value slot inside the renderer. The slot is initialised once on the first render — every subsequent render returns the same getter and setter with no extra allocations.

Handles/TabsHandle.luau
return Handle(function(element: Frame)
    local activeTab, setActiveTab = Handle:bind("General")

    label.Text = `Active: {activeTab()}`

    Handle.Connect("GeneralTab", generalBtn.Activated, function()
        setActiveTab("General")
    end)

    Handle.Connect("ShopTab", shopBtn.Activated, function()
        setActiveTab("Shop")
    end)
end)

Binding order

Always call bind (and all other bindings like derive, hold, await) at the top of the renderer, unconditionally, in the same order every render. Conditional or reordered bindings will read from the wrong slot.


Side effects with Effect

Handle.Effect registers a side effect that runs after every render. Return a cleanup function to have it called before the next render.

Handle.Effect(function()
    local conn = RunService.Heartbeat:Connect(function()
        element.Text = os.date("%H:%M:%S")
    end)

    return function()
        conn:Disconnect()
    end
end)

One-time setup with Once

Handle.Once runs its callback exactly once — on the first render that execution reaches that call.

Handle.Once(function()
    for i, item in ipairs(catalogData) do
        table.insert(cachedItems, item)
    end
end)

Named connections with Connect

Handle.Connect wires a signal under a named slot. The previous connection for that name is automatically disconnected on each render.

local closeBtn = element.TopBar.Close :: TextButton
Handle.Connect("Close", closeBtn.Activated, function()
    Fragment.getHandle("Shop"):Toggle(false)
end)

Named actions

Actions let other parts of your code call into a Handle from outside the renderer. They are registered once and remain callable even when the element is hidden.

Handles/ShopHandle.luau
Handle.Action("Toggle", function(state: boolean)
    element.Visible = state
end)
Handles/HUDHandle.luau
Fragment.getHandle("Shop"):Toggle(true)

Dynamic children with RegisterContainer

Handle.RegisterContainer returns a Container that diffs children across renders. It can be called at the top level of a renderer or inside an Effect callback.

Handles/ShopHandle.luau
return Handle(function(element: Frame)
    local store = Fragment.useStore("Shop")
    local data  = store.getData()

    local grid = Handle.RegisterContainer(element.ItemGrid)

    for _, item in ipairs(data.items) do
        local card = Fragment.useComponent("ItemCard", { 
            Id    = item.id, 
            Name  = item.displayName, 
            Price = item.price, 
        }) 
        grid.UpsertChild(item.id, card) 
    end
end)

Full example

Handles/ShopHandle.luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Fragment = require(ReplicatedStorage.Fragment)

local Handle = Fragment.newHandle("Shop", { "HUD", "Shop", "MainFrame" })

return Handle(function(element: Frame)
    local activeTab, setActiveTab = Handle:bind("General")

    Handle.Action("Toggle", function(state: boolean)
        element.Visible = state
    end)

    local closeBtn = element.TopBar.Close :: TextButton
    Handle.Connect("Close", closeBtn.Activated, function()
        Fragment.getHandle("Shop"):Toggle(false)
    end)

    element.Title.Text = `{activeTab()} — Shop`

    Handle.Connect("GeneralTab", element.Tabs.General.Activated, function()
        setActiveTab("General")
    end)

    Handle.Connect("FeaturedTab", element.Tabs.Featured.Activated, function()
        setActiveTab("Featured")
    end)
end)

For the full API see the Handle reference.

On this page