Building Studio Widgets

Studio gives you the power to create custom widgets and use them as Studio tools and extensions. These widgets behave as custom windows/panels in Studio, and you can dock them inside of your interface or have them float as separate windows.

Creating Widget UIs

All Studio widgets begin as DockWidgetPluginGui objects which you can fill with GuiObjects, such as text labels and buttons. To create an empty widget GUI, call the CreateDockWidgetPluginGui() function, passing in an ID and a DockWidgetPluginGuiInfo object.

Note the constructor expects its parameters in a specific order as follows:

# Property Type Description
1 InitialDockState Enum One of the InitialDockState enumerations.
2 InitialEnabled Boolean The initial enabled (visible) state of the widget GUI.
3 InitialEnabledShouldOverrideRestore Boolean If true, the value of InitialEnabled overrides the previously saved enabled state.
4 FloatingXSize Integer The initial width of the GUI when InitialDockState is set to InitialDockState.Float.
5 FloatingYSize Integer The initial height of the GUI when InitialDockState is set to InitialDockState.Float.
6 MinWidth Integer The minimum width of the GUI, with some platform-specific variations.
7 MinHeight Integer The minimum height of the GUI, with some platform-specific variations.

1-- Create new "DockWidgetPluginGuiInfo" object
2local widgetInfo =
3 Enum.InitialDockState.Float, -- Widget will be initialized in floating panel
4 true, -- Widget will be initially enabled
5 false, -- Don't override the previous enabled state
6 200, -- Default width of the floating window
7 300, -- Default height of the floating window
8 150, -- Minimum width of the floating window
9 150 -- Minimum height of the floating window
12-- Create new widget GUI
13local testWidget = plugin:CreateDockWidgetPluginGui("TestWidget", widgetInfo)
14testWidget.Title = "Test Widget" -- Optional widget title

Customizing Widget UI

Once you create a widget, you can customize its user interface with GuiObjects such as informative TextLabels or interactive ImageButtons. For example, the following code adds a basic TextButton to the GUI window:

1-- Create new widget GUI
2local testWidget = plugin:CreateDockWidgetPluginGui("TestWidget", widgetInfo)
3testWidget.Title = "Test Widget" -- Optional widget title
5local testButton ="TextButton")
6testButton.BorderSizePixel = 0
7testButton.TextSize = 20
8testButton.TextColor3 =,0.2,0.4)
9testButton.AnchorPoint =,0.5)
10testButton.Size =,0,1,0)
11testButton.Position =,0,0.5,0)
12testButton.SizeConstraint = Enum.SizeConstraint.RelativeYY
13testButton.Text = "Click Me"
14testButton.Parent = testWidget

Changing Studio Color Themes

Effective Studio widgets ideally match the Studio theme setting and dynamically adjust when the theme changes. For instance, if a developer is using the dark theme, the widget's background color, images, and text labels should look nice alongside Studio's native theme colors.

The following code addition uses a syncGuiColors() function which is initially called along with a table of GUI objects to sync. Inside the function, a nested setColors() function loops through the objects and syncs specific aspects of them using GetColor() with StudioStyleGuideColor enums. This setColors() function is immediately run to sync the Studio theme, then it's connected to the ThemeChanged event to detect future theme changes.

1testButton.Parent = testWidget
3local function syncGuiColors(objects)
4 local function setColors()
5 for _, guiObject in pairs(objects) do
6 -- Sync background color
7 guiObject.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.MainBackground)
8 -- Sync text color
9 guiObject.TextColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.MainText)
10 end
11 end
12 -- Run 'setColors()' function to initially sync colors
13 setColors()
14 -- Connect 'ThemeChanged' event to the 'setColors()' function
15 settings().Studio.ThemeChanged:Connect(setColors)
18-- Run 'syncGuiColors()' function to sync colors of provided objects

Customizing Mouse Cursors

To improve the expected interaction with widget elements, you can set system-specific mouse cursors to GUI events, such as MouseEnter and MouseLeave. The following code demonstrates how to connect a function to the MouseEnter and MouseLeave events of testButton to change the mouse cursor:

1local function setCursor(cursorAsset)
2 plugin:GetMouse().Icon = cursorAsset
6 setCursor("rbxasset://SystemCursors/PointingHand")
9 setCursor("")

Reference the following table for a list of mouse cursors and their potential use cases:

Mouse Cursor Icon Asset Use Case
rbxasset://SystemCursors/Arrow Default clicking and selection.
rbxasset://SystemCursors/PointingHand Hovering over an active link/button.
rbxasset://SystemCursors/OpenHand Hovering over a draggable item.
rbxasset://SystemCursors/ClosedHand Dragging an item.
rbxasset://SystemCursors/IBeam Hovering in a text field.
rbxasset://SystemCursors/SizeNS Hovering over a vertical resize handle.
rbxasset://SystemCursors/SizeEW Hovering over a horizontal resize handle.
rbxasset://SystemCursors/SizeNESW Hovering over a corner resize handle.
rbxasset://SystemCursors/SizeNWSE Hovering over a corner resize handle.
rbxasset://SystemCursors/SizeAll Hovering over a multi-direction resize handle.
rbxasset://SystemCursors/SplitNS Hovering over a vertical "split" handle.
rbxasset://SystemCursors/SplitEW Hovering over a horizontal "split" handle.
rbxasset://SystemCursors/Forbidden Hovering over a locked/forbidden item.
rbxasset://SystemCursors/Wait Indicating an action is in progress.
rbxasset://SystemCursors/Busy Indicating the system is busy.
rbxasset://SystemCursors/Cross Hovering over a pinpoint selection area.

Gathering User Input

UI elements such as TextBox and TextButton work normally in Studio widgets, and you can build interfaces just like you normally would on Roblox. However, UserInputService and ContextActionService don't work since these services expect the main game window to be in focus.

One workaround for generic input events is to create a transparent Frame and overlay it over the entire screen. The following code sample creates a frame, and when the user clicks on the frame, the GuiObject.InputBegan event captures keyboard input on the frame until the user clicks away:

1local frame ="Frame")
2frame.BackgroundTransparency = 1 -- Hide the frame
3frame.Size =, 0, 1, 0) -- Cover the screen
4frame.Position =, 0, 0, 0)
5frame.Parent = testWidget
7local function onInputBegan(inputObject)
8 -- Process the input object here, for example detect key presses

Drag-and-Drop Interaction

Using drag-and-drop interactions for your widgets is a simple way to improve the flow of data. To create this interaction, you must define the element to drag, initiate the drag, create a drop target, and process the drop action.

Creating a Drag Source

You can start a drag action by calling Plugin:StartDrag() when the user presses a mouse button on some UI element, typically a TextButton or ImageButton within a widget. The following code sample creates a single window widget with a text button inside it.

1-- Create the widget first
2local widgetInfo =, true, true, 300, 200)
3local dragSourceWidget = plugin:CreateDockWidgetPluginGui("Drag Source", widgetInfo)
4dragSourceWidget.Title = "Drag Source"
6-- Create a TextButton that will initiate the drag
7local dragButton ="TextButton")
8dragButton.Size =, 0, 1, 0)
9dragButton.Text = "Drag me!"
10dragButton.Parent = dragSourceWidget

Initiating the Drag

When the user clicks on the TextButton, you can initiate drag through the MouseButton1Down() event which fires as soon as the user presses the mouse button.

Within the connected function, determine the data to drag. The data's type should be reflected in the MimeType key, the content of the drag should be reflected within the Data key, and the sender should describe itself in the Sender key. See the Plugin:StartDrag() page for more details.

1local function onButton1Down()
2 local dragInfo = {
3 Data = "Hello, world", -- The data being dragged
4 MimeType = "text/plain", -- Describes the MIME type of the data
5 Sender = "SomeDragSource", -- Describes from where the data originated
6 MouseIcon = "", -- Image content to use for the cursor
7 DragIcon = "", -- Image content to render under the cursor during drag
8 HotSpot =, 0) -- Where on the DragIcon to center the cursor
9 }
10 plugin:StartDrag(dragInfo)

Creating a Drop Target

The PluginGui.PluginDragDropped event fires when the user releases their mouse on a window during a drag. When this occurs, you need to define a drop target such as a second widget with a TextLabel to detect drops.

1local dragTargetWidget = plugin:CreateDockWidgetPluginGui("Drop Target", widgetInfo)
2dragTargetWidget.Title = "Drop Target"
4-- This TextLabel will display what was dropped
5local textLabel ="TextLabel")
6textLabel.Size =, 0, 1, 0)
7textLabel.Text = "Drop here..."
8textLabel.Parent = dragTargetWidget

Processing the Drop Action

After creating a drop target, connect the PluginGui.PluginDragDropped event on the drop target widget:

1local function onDragDrop(dragData)
2 print("PluginDragDropped")
3 if dragData.MimeType == "text/plain" then
4 textLabel.Text = dragData.Data
5 else
6 textLabel.Text = dragData.MimeType
7 end

While a drag is still in progress, these three events fire as the user moves their mouse over a widget:

  • PluginDragEntered – fires when the user hovers the mouse over a window
  • PluginDragMoved – fires repeatedly as the user moves their mouse over a window. This is useful for showing a "Drop here!" message.
  • PluginDragLeft – fires when the user's cursor leaves a window. This is useful for hiding a "Drop here!" message.