React Components for Roblox Studio Plugins

For those who’d prefer, a version of the article is available on Medium.

I’ve got tired of having to deal with the imperative plugin APIs, so I created this open-source package to tightly integrate React to Roblox Studio plugin development. If you are already using React for plugin development, you will find this package very useful to eliminate a large portion of the bloat that comes with the initial plugin setup.

Why React for Roblox Plugins?

  • Build plugins declaratively: easily manage plugin widget states, toolbars, buttons and actions.
  • If you are already using React for other projects, you can re-use your generic components for plugin development.
  • Gain access to great community built React libraries like:

Getting Started

Plugin Main Script

The main script will look very similar to any plugin built with React. As usual, it will use react-roblox to create a root and render your main component.

All that is needed is to wrap your main component with the RobloxPlugin provider.

local React = require('@pkg/@jsdotlua/react')
local ReactRoblox = require('@pkg/@jsdotlua/react-roblox')
local ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')

local PluginApp = require('./PluginApp')

local RobloxPlugin = ReactRobloxStudioPlugin.RobloxPlugin

local container = Instance.new('Folder')
container.Name = 'PluginView'
local root = ReactRoblox.createRoot(container)

local element = React.createElement(
	RobloxPlugin, 
	{ plugin = plugin },
	React.createElement(PluginApp)
)

root:render(element, container)

Create a Button Inside a Toolbar

Now let’s look at an example of how the PluginApp component could look. In this example, the plugin will simply create a toolbar and a button.
`

local React = require('@pkg/@jsdotlua/react')
local ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')

local Toolbar = ReactRobloxStudioPlugin.Toolbar
local ToolbarButton = ReactRobloxStudioPlugin.ToolbarButton

local function PluginApp()
	return React.createElement(
		Toolbar, 
		{ name = "Toolbar Example" },
		React.createElement(ToolbarButton, {
			id = "first-button",
			text = "Click",
			onClick = function()
				print("Button clicked!")
			end,
		})
	)
end

return PluginApp

Create a Plugin Widget

Let’s push things a little further and add a DockWidgetPluginGui connected to a button inside a toolbar.

local React = require('@pkg/@jsdotlua/react')
local ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')
local ReactLuaHooks = require('@pkg/@seaofvoices/react-lua-hooks')

local useToggle = ReactLuaHooks.useToggle
local Toolbar = ReactRobloxStudioPlugin.Toolbar
local ToolbarButton = ReactRobloxStudioPlugin.ToolbarButton
local Widget = ReactRobloxStudioPlugin.Widget

local function PluginApp()
	-- useToggle is simply a tiny wrapper around a useState
	-- from https://github.com/seaofvoices/react-lua-hooks
	-- that gives a toggle, on and off functions for a boolean
	local widgetEnabled, setWidgetState = useToggle(false)

    return React.createElement(
	    React.Fragment, 
	    nil,
        React.createElement(
            Toolbar,
            { name = "Toolbar Example" },
            React.createElement(ToolbarButton, {
                id = "toggle-widget",
                text = if widgetEnabled then "Open Widget" else "Close Widget",
                onClick = setWidgetState.toggle,
            })
        ),
        React.createElement(
            Widget,
            {
                id = "example-widget",
                title = "Example Widget",
                enabled = widgetEnabled,
                minSize = Vector2.new(200, 200),
                initialDockState = Enum.InitialDockState.Float,
                onClose = setWidgetState.off,
            },
            React.createElement("TextLabel", {
                Text = "This is a widget!",
                Size = UDim2.new(1, 0, 0, 30),
                BackgroundTransparency = 1,
            })
        )
    )
end

return PluginApp

Access the plugin Global

At some point, you may need to call some plugin specific functions from the plugin global variable (like plugin:GetMouse() or plugin:OpenScript()). To do that, use the usePlugin hook:

local React = require('@pkg/@jsdotlua/react')
local ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')

local usePlugin = ReactRobloxStudioPlugin.usePlugin

local function ComponentExample(props)
	local plugin = usePlugin()

	-- ...
end

API Reference

There is even more to the library! I invite you to visit the components and hooks reference on GitHub to learn more.

Installation

This library is available on npm:

npm install @seaofvoices/react-roblox-studio-plugin
# or if you are using yarn
yarn add @seaofvoices/react-roblox-studio-plugin

The easiest way to get started with npm is with generator-luau, a Luau project generator that creates all the necessary configuration files to have luau-lsp, selene, stylua, darklua and even jest-lua. You can learn more about this in this article.

Feedback

If you have any suggestions of improvements or issues to report, feel free to visit the GitHub issues list. Create a new issue, comment or add a reaction to an existing one to show your interests.

For broader discussion or questions, you can of course reply to this post or catch me in the Roblox open-source Discord server.

End Notes

I work part time to dedicate time to the Luau open-source ecosystem. If you find this library or my other projects helpful, please consider buying me a coffee on ko-fi :coffee:

I would love to see your plugins created with this library. It’s always great and encouraging to see what the community builds! :building_construction:

To learn more about Luau on npm:

Useful Links

7 Likes