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.

In a typical Roact tree every component’s output is placed inside its parent’s Instance. Portals break that rule intentionally: a portal renders its children into a different Roblox Instance that you specify, regardless of where the portal component sits in the component tree. This is especially useful for full-screen UI layers, global overlays, and any case where a component deep in the tree needs to own an object that must live somewhere else in the DataModel.

Creating a Portal

Use Roact.Portal as the first argument to Roact.createElement. The target prop must be a Roblox Instance that will become the parent of the portal’s children.
local function PartInWorkspace(props)
    return Roact.createElement(Roact.Portal, {
        target = Workspace,
    }, {
        SomePart = Roact.createElement("Part", {
            Anchored = true,
        }),
    })
end
When PartInWorkspace is mounted — no matter how deeply nested it is in the Roact tree — Roact creates a Part named SomePart directly inside Workspace. When PartInWorkspace unmounts, Roact removes that Part.
Portals should only target Roblox Instances that are not managed by Roact. Pointing a portal at an Instance that Roact already owns (such as one created by another createElement call) will cause both systems to fight over the same children, leading to unpredictable behaviour and errors.

Use Case: Full-Screen Modal Dialogs

Portals shine when a component deep inside the tree needs to display a full-screen overlay. Without portals you would have to thread a ScreenGui up through every layer of the hierarchy. With a portal the Modal component handles everything itself.

The Modal Component

local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui

-- Modal is a plain component that renders its ScreenGui directly into PlayerGui
-- via a portal — no matter where Modal appears in the Roact tree.
local function Modal(props)
    return Roact.createElement(Roact.Portal, {
        target = PlayerGui,
    }, {
        Modal = Roact.createElement("ScreenGui", {}, {
            Label = Roact.createElement("TextButton", {
                Size = UDim2.new(1, 0, 1, 0),
                Text = "Click me to close!",

                [Roact.Event.Activated] = function()
                    props.onClose()
                end,
            }),
        }),
    })
end

The ModalButton Stateful Component

ModalButton keeps a small piece of state — whether the dialog is currently open — and conditionally renders the Modal component.
local ModalButton = Roact.Component:extend("ModalButton")

function ModalButton:init()
    self.state = {
        dialogOpen = false,
    }
end

function ModalButton:render()
    local dialog = nil

    -- Only create the modal element when the dialog should be visible.
    if self.state.dialogOpen then
        dialog = Roact.createElement(Modal, {
            onClose = function()
                self:setState({
                    dialogOpen = false,
                })
            end,
        })
    end

    return Roact.createElement("TextButton", {
        Size = UDim2.new(0, 400, 0, 300),
        Text = "Click me to open modal dialog!",

        [Roact.Event.Activated] = function()
            self:setState({
                dialogOpen = true,
            })
        end,
    }, {
        -- When `dialog` is nil this slot is simply ignored by Roact.
        Dialog = dialog,
    })
end
When the user clicks ModalButton, dialogOpen becomes true, Roact renders the Modal component, and the portal inserts a ScreenGui into PlayerGui — covering the entire screen. Clicking the overlay fires onClose, sets dialogOpen back to false, and Roact removes the ScreenGui from PlayerGui automatically.

How Roact Handles Portal Lifecycle

Roact treats portal children like any other managed subtree:
  • On mount — children are created as descendants of target.
  • On update — children are reconciled in place inside target, following the same rules as a normal subtree.
  • On unmount — all children created by the portal are destroyed.
The only difference is where those children live in the DataModel. From Roact’s perspective the portal is just another node in the virtual tree.

Build docs developers (and LLMs) love