-
Notifications
You must be signed in to change notification settings - Fork 0
React Roblox
@nrbx/react-roblox is the Roblox renderer for @nrbx/react. It is the equivalent of react-dom for the web: it mounts React trees into Roblox Instance objects, reconciles updates, and manages cleanup for the UI you render.
At a high level, it gives you:
-
createRoot(container)to attach a React tree to a Roblox container -
createPortal(children, container)to render outside the normal parent tree -
flushSync(callback)to force immediate synchronous updates -
createBlockingRoot()andcreateLegacyRoot()for compatibility with older rendering modes
Install the core React package and the Roblox renderer:
npm install @nrbx/react @nrbx/react-robloxWith Yarn or pnpm:
yarn add @nrbx/react @nrbx/react-roblox
pnpm add @nrbx/react @nrbx/react-robloxYou can then render into any Roblox Instance that makes sense for the UI you are building:
-
ScreenGuifor HUD, menus, and full-screen overlays -
Frame/Folderfor nested UI trees -
BillboardGuifor world-space avatar/world UI -
SurfaceGuifor placing UI on a Part surface
import React from "@nrbx/react";
import { createRoot } from "@nrbx/react-roblox";
function App() {
return (
<frame className="p-8">
<textlabel Text="Hello, Roblox!" className="text-2xl font-bold" />
</frame>
);
}
const container = script.Parent!.WaitForChild("ScreenGui") as ScreenGui;
const root = createRoot(container);
root.render(<App />);This creates a root for the target Instance and renders your app tree underneath it.
Creates a root for a Roblox Instance and returns an object with:
-
render(element)— render or re-render the React tree -
unmount()— remove the tree and clean up React-managed instances
import React from "@nrbx/react";
import { createRoot } from "@nrbx/react-roblox";
const root = createRoot(new Instance("ScreenGui"));
root.render(
<frame Size={new UDim2(1, 0, 1, 0)} BackgroundColor3={Color3.fromRGB(15, 23, 42)}>
<textlabel
Size={new UDim2(1, 0, 0, 40)}
Position={new UDim2(0, 0, 0, 10)}
Text="Mounted via createRoot"
TextColor3={Color3.fromRGB(255, 255, 255)}
BackgroundTransparency={1}
/>
</frame>,
);Use one root per container. A root owns the subtree under that Instance and is the intended place to call render and unmount.
This is the legacy blocking root mode. It is useful for compatibility with older patterns and React 17-style behavior. Prefer createRoot for new code unless you specifically need the older semantics.
This is the legacy root mode that includes older behavior such as string refs support. It is mainly for compatibility with older React patterns and is not the default choice for most new Roblox apps.
The root is the entry point for a React-managed UI subtree.
This example renders a counter:
import React, { useState } from "@nrbx/react";
import { createRoot } from "@nrbx/react-roblox";
function Counter() {
const [count, setCount] = useState(0);
return (
<frame Size={new UDim2(0, 220, 0, 100)} BackgroundColor3={Color3.fromRGB(15, 23, 42)}>
<textlabel
Size={new UDim2(1, 0, 0, 40)}
Position={new UDim2(0, 0, 0, 0)}
Text={`${count}`}
TextColor3={Color3.fromRGB(255, 255, 255)}
BackgroundTransparency={1}
/>
<textbutton
Size={new UDim2(0, 120, 0, 32)}
Position={new UDim2(0.5, -60, 1, -40)}
AnchorPoint={new Vector2(0.5, 1)}
Text="Increment"
Event={{
Activated: () => setCount((value) => value + 1),
}}
/>
</frame>
);
}
const gui = new Instance("ScreenGui");
gui.Parent = game.GetService("Players").LocalPlayer!.WaitForChild("PlayerGui");
const root = createRoot(gui);
root.render(<Counter />);After the initial render, call root.render() again with a new element tree whenever you want to update the UI. React will reconcile changes against the existing Roblox tree.
root.render(<Counter />);This is the equivalent of a render call in React DOM, except the target is a Roblox Instance instead of a browser DOM element.
Portals let you render children into a different Roblox instance, which is useful for overlays such as:
- modals
- dropdown menus
- tooltips
- floating notifications
- custom context overlays
The API is:
createPortal(children, container)Example:
import React, { useMemo } from "@nrbx/react";
import { createPortal } from "@nrbx/react-roblox";
function Modal({ children }: { children: React.ReactNode }) {
const gui = useMemo(() => {
const sg = new Instance("ScreenGui");
sg.Parent = game.GetService("Players").LocalPlayer!.WaitForChild("PlayerGui");
return sg;
}, []);
return createPortal(
<frame className="bg-black/50 w-screen h-screen flex items-center justify-center">
<frame className="bg-white p-6 rounded-lg shadow-lg">
{children}
</frame>
</frame>,
gui,
);
}Portal tips:
- Use a dedicated
ScreenGuifor overlays so they are not constrained by the parent layout tree. - For world-space elements, a
BillboardGuiorSurfaceGuiis often a better target than aScreenGui. - Keep the portal container alive for as long as the overlay is visible.
flushSync(callback) forces React to flush pending state updates synchronously.
import { flushSync } from "@nrbx/react-roblox";
function openPanel() {
flushSync(() => {
setOpen(true);
});
// UI is already committed and measurements can be read immediately
print(panel.AbsoluteSize);
}Use it for cases where timing matters, such as:
- animations that need a committed frame before measuring
- immediate layout reads after state changes
- synchronizing UI before a physics or camera update
- imperative work that must happen right after render
Be careful: flushSync can hurt performance if used too aggressively. Prefer it only when you truly need immediate synchronization.
@nrbx/react-roblox is conceptually the Roblox equivalent of react-dom.
The differences are mainly in the host environment:
Web (react-dom) |
Roblox (@nrbx/react-roblox) |
|---|---|
Mounts into DOM Element nodes |
Mounts into Roblox Instance objects |
document.body or a DOM container |
ScreenGui, Frame, BillboardGui, SurfaceGui, etc. |
| Portal container is a DOM node | Portal container is a Roblox Instance
|
| Browser event model | Roblox Event and Change handlers |
| Layout is CSS-driven | Layout and Roblox GUI properties are expressed through instance props and class names |
The mental model is still the same: component trees, props, state updates, effects, portals, and error boundaries, but the host target is Roblox UI instead of the browser DOM.
Choosing the right container matters a lot in Roblox UI.
Best for HUD, menus, loading screens, and full-screen overlays.
const playerGui = game.GetService("Players").LocalPlayer!.WaitForChild("PlayerGui");
const root = createRoot(playerGui);
root.render(<App />);Use ScreenGui when the UI should follow screen space and not world space.
Best for labels or floating UI that should appear attached to a part or character in 3D space.
const billboard = new Instance("BillboardGui");
billboard.Adornee = workspace.Part;
const root = createRoot(billboard);
root.render(<textlabel Text="Player HP" />);Best for UI displayed on the surface of a part.
const surface = new Instance("SurfaceGui");
surface.Face = Enum.NormalId.Front;
surface.Adornee = workspace.Part;
const root = createRoot(surface);
root.render(<frame Size={new UDim2(1, 0, 1, 0)} />);Use Frame, Folder, or other GUI containers when you want to render a subtree inside a larger UI tree. This is useful when building reusable panels or nested app sections.
You can create multiple roots on different containers and manage them independently.
import React from "@nrbx/react";
import { createRoot } from "@nrbx/react-roblox";
const hudGui = new Instance("ScreenGui");
hudGui.Parent = game.GetService("Players").LocalPlayer!.WaitForChild("PlayerGui");
const panelGui = new Instance("Frame");
panelGui.Parent = hudGui;
const hudRoot = createRoot(hudGui);
const panelRoot = createRoot(panelGui);
hudRoot.render(<hud />);
panelRoot.render(<panel />);This is useful when you have:
- separate HUD layers
- multiple modals or menus
- independent sub-apps within the same screen
- partial tree re-renders without destroying unrelated UI
Every root is isolated and can be unmounted independently.
unmount() removes the entire React-managed subtree for that root and cleans up any React-owned Roblox instances it created.
const root = createRoot(container);
root.render(<App />);
root.unmount();When you are done with a root, call unmount() before destroying the container instance itself. This ensures event listeners and managed descendants are properly cleaned up.
For example:
const gui = new Instance("ScreenGui");
const root = createRoot(gui);
root.render(<App />);
root.unmount();
gui:Destroy();This workflow avoids leaving behind stale UI or detached event handlers.
@nrbx/react-roblox supports React-style error boundaries. If a render error occurs in a subtree, a boundary can catch it and render a fallback instead of crashing the rest of the tree.
import React from "@nrbx/react";
class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <textlabel Text="Something went wrong" />;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<BrokenWidget />
</ErrorBoundary>
);
}This prevents a single component failure from taking down the entire UI tree, which is especially helpful in game UIs that need to degrade gracefully under unexpected runtime errors.
- Use
createRootfor most cases; prefercreateBlockingRootandcreateLegacyRootonly when compatibility is required. - Keep root containers focused: use a
ScreenGuifor main HUD, aBillboardGuifor 3D overlays, and aSurfaceGuifor part surfaces. - Use portals for overlays that should not be constrained by local layout.
- Call
unmount()when a root is no longer needed. - Keep
flushSyncnarrow and deliberate; avoid using it everywhere.
@nrbx/react-roblox provides the Roblox host layer for React-style UI development. It is the runtime that:
- mounts component trees into Roblox
Instances - manages updates and lifecycle
- supports portals, multiple roots, and cleanup
- works with Roblox GUI APIs and game UI patterns
If you are familiar with react-dom, the transition to @nrbx/react-roblox is straightforward: the same React concepts, but rendered into Roblox instances instead of browser DOM nodes.