Forest Logo
search
package_2

uiservice

By @realstencer

Roblox

Mirrored

uiservice

uiservice is a small helper framework for Roblox UI built on top of React, ReactRoblox, Ripple, and ReactCharm.

It keeps the original libraries available, but adds a simpler layer for the repetitive parts:

  • quick element creation with uiservice.e
  • ready-made UI constructors in uiservice.components using Roblox instance names
  • automatic default props when you call e("TextLabel", { ... }) or e(TextLabel, { ... })
  • optional prop presets in uiservice.props
  • optional helpers in uiservice.values
  • easy mounting with uiservice.mount
  • lightweight Charm-backed stores with uiservice.createStore
  • Ripple bindings for React with uiservice.useSpring, uiservice.useTween, and uiservice.useMotion

This package is meant to be a helper and easy starting point. It is not a replacement for React, ReactRoblox, Ripple, or Charm.

Install

[dependencies]
uiservice = "realstencer/uiservice@0.1.0"

What It Exports

local UIService = require(Packages.uiservice)

local React = UIService.React
local e = UIService.e
local TextLabel = UIService.TextLabel
local TextButton = UIService.TextButton
local TextBox = UIService.TextBox
local CanvasGroup = UIService.CanvasGroup
local ScreenGui = UIService.ScreenGui
local UICorner = UIService.UICorner

Main exports:

  • React
  • ReactRoblox
  • ReactCharm
  • Ripple
  • Charm
  • e(component, props, children?)
  • createComponent(className, defaults?)
  • components
  • props
  • values
  • Roblox-named built-ins directly on UIService, such as TextLabel, TextButton, TextBox, CanvasGroup, ViewportFrame, VideoFrame, Frame, ScreenGui, UICorner, UIPadding, UIListLayout, UIGridLayout, UIPageLayout, and UIStroke
  • top-level helpers like FontFace, AnchorPoint, Position, and Size
  • mount(target, element)
  • unmount(target)
  • createRoot(target)
  • createStore(initialState)
  • createAtom(initialValue, options?)
  • createComputed(callback, options?)
  • useAtom(atomOrSelector, dependencies?)
  • useSpring(initialValue, options?, dependencies?)
  • useTween(initialValue, options?, dependencies?)
  • useMotion(initialValue, options?, dependencies?)
  • useAnimatedBinding(initialValue, factory, dependencies?)
  • useScaleButton(options?)

Why Use It

Use uiservice when you want to keep real Roblox names like TextLabel, TextButton, ImageButton, UICorner, and UIPadding, but still move faster.

It helps in three main ways:

  • components gives you Roblox-named constructors such as ui.TextLabel and ui.TextButton
  • props gives you a basic ordered set of common properties for each instance
  • values gives you autocomplete-friendly helpers for Color3, Font, UDim2, size, position, anchor points, and common UI enums

Example: Simple TextLabel

local UIService = require(Packages.uiservice)

local e = UIService.e
local TextLabel = UIService.TextLabel


local element = e(TextLabel, {
	AnchorPoint = Vector2.new(0.5, 0.5),
	Position = UDim2.fromScale(0.5, 0.1),
	Size = UDim2.fromScale(0.4, 0.08),
	Text = "Title",
	TextColor3 = Color3.fromRGB(255, 255, 255),
	FontFace = Font.fromName("Montserrat"),
})

The automatic default props for TextLabel are still applied even if you only override a few fields. The current default set is based on src/Props.luau.

More Supported Classes

The helper now includes automatic presets for:

  • Frame
  • TextLabel
  • TextButton
  • TextBox
  • ImageLabel
  • ImageButton
  • ScrollingFrame
  • CanvasGroup
  • ViewportFrame
  • VideoFrame
  • ScreenGui
  • UICorner
  • UIPadding
  • UIListLayout
  • UIGridLayout
  • UIPageLayout
  • UIAspectRatioConstraint
  • UISizeConstraint
  • UIScale
  • UIStroke
  • UIGradient

Example: Using Direct Roblox Names

local UIService = require(Packages.uiservice)

local e = UIService.e
local TextLabel = UIService.TextLabel
local TextButton = UIService.TextButton
local ScreenGui = UIService.ScreenGui
local UICorner = UIService.UICorner

local function MainMenu()
	return e(ScreenGui, {
		DisplayOrder = 10,
	}, {
		Title = e(TextLabel, {
			AnchorPoint = Vector2.new(0.5, 0),
			Position = UDim2.fromScale(0.5, 0.08),
			Size = UDim2.fromScale(0.4, 0.08),
			Text = "Main Menu",
			TextColor3 = Color3.fromRGB(255, 255, 255),
			FontFace = Font.fromName("Montserrat"),
			TextScaled = true,
		}),
		PlayButton = e(TextButton, {
			AnchorPoint = Vector2.new(0.5, 0.5),
			Position = UDim2.fromScale(0.5, 0.5),
			Size = UDim2.fromOffset(220, 54),
			BackgroundColor3 = Color3.fromRGB(66, 135, 245),
			Text = "Play",
			TextColor3 = Color3.fromRGB(255, 255, 255),
			onActivated = function()
				print("Play clicked")
			end,
		}, {
			Corner = e(UICorner, {
				CornerRadius = UDim.new(0, 10),
			}),
		}),
	})
end

Example: Optional Preset Builder

If you do want a prefilled ordered prop table, props is still available:

local props = UIService.props
local TextLabel = UIService.TextLabel

local Title = e(TextLabel, props.TextLabel({
	Text = "Title",
	FontFace = Font.fromName("Montserrat"),
}))

Example: Mount To PlayerGui

local Players = game:GetService("Players")
local UIService = require(Packages.uiservice)

local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
UIService.mount(playerGui, MainMenu())

Example: Game State With ReactCharm

local UIService = require(Packages.uiservice)

local store = UIService.createStore({
	coins = 0,
	menuOpen = true,
	selectedTab = "Home",
})

store.computed("title", function()
	return store.get("selectedTab") .. " Menu"
end)

local function Header()
	local coins = store.use("coins")
	local title = store.use("title")

	return UIService.e(UIService.TextLabel, {
		BackgroundTransparency = 1,
		Size = UDim2.new(1, 0, 0, 36),
		Text = string.format("%s | Coins: %d", title, coins),
		TextColor3 = Color3.fromRGB(255, 255, 255),
		TextScaled = false,
		TextSize = 22,
	})
end

store.set("coins", function(value)
	return value + 50
end)

Example: Ripple Animation In React

local UIService = require(Packages.uiservice)

local React = UIService.React
local e = UIService.e
local TextButton = UIService.TextButton
local UICorner = UIService.UICorner

local function AnimatedPlayButton()
	local scale, spring, buttonProps = UIService.useScaleButton({
		hoverScale = 1.04,
		pressScale = 0.95,
	})

	return e(TextButton, {
		AnchorPoint = Vector2.new(0.5, 0.5),
		Position = UDim2.fromScale(0.5, 0.5),
		Size = scale:map(function(value)
			return UDim2.fromOffset(240 * value, 64 * value)
		end),
		BackgroundColor3 = Color3.fromRGB(76, 201, 126),
		Text = "Play",
		TextColor3 = Color3.fromRGB(18, 24, 18),
		TextScaled = true,
		AutoButtonColor = false,
		[React.Change.GuiState] = buttonProps[React.Change.GuiState],
		Activated = function()
			spring:setGoal(1.08)
			task.delay(0.08, function()
				spring:setGoal(1)
			end)
		end,
	}, {
		Corner = e(UICorner, {
			CornerRadius = UDim.new(0, 12),
		}),
	})
end

Full Example: Animated Hover And Press Button

This is the pattern for a button that grows on hover and compresses on press.

local UIService = require(Packages.uiservice)

local React = UIService.React
local e = UIService.e
local TextButton = UIService.TextButton
local UICorner = UIService.UICorner
local UIStroke = UIService.UIStroke

local function SpringButton(props)
	local scale, spring, hoverProps = UIService.useScaleButton(UIService.props.TextButtonHoverScale({
		hoverScale = 1.05,
		pressScale = 0.93,
		restScale = 1,
	}))

	return e(TextButton, {
		AnchorPoint = Vector2.new(0.5, 0.5),
		Position = props.Position or UDim2.fromScale(0.5, 0.5),
		Size = scale:map(function(value)
			return UDim2.fromOffset(220 * value, 56 * value)
		end),
		BackgroundColor3 = props.BackgroundColor3 or Color3.fromRGB(66, 135, 245),
		Text = props.Text or "Play",
		TextColor3 = Color3.fromRGB(255, 255, 255),
		FontFace = Font.fromName("Montserrat"),
		[React.Change.GuiState] = hoverProps[React.Change.GuiState],
		Activated = function()
			spring:setGoal(0.9)
			task.delay(0.06, function()
				spring:setGoal(1)
			end)

			if props.Activated then
				props.Activated()
			end
		end,
	}, {
		Corner = e(UICorner, {
			CornerRadius = UDim.new(0, 12),
		}),
		Stroke = e(UIStroke, {
			Color = Color3.fromRGB(255, 255, 255),
			Transparency = 0.35,
		}),
	})
end

Full Example: Shop UI With State And Animation

local UIService = require(Packages.uiservice)

local React = UIService.React
local e = UIService.e
local ScreenGui = UIService.ScreenGui
local Frame = UIService.Frame
local TextLabel = UIService.TextLabel
local TextButton = UIService.TextButton
local UIListLayout = UIService.UIListLayout
local UIPadding = UIService.UIPadding
local UICorner = UIService.UICorner
local UIStroke = UIService.UIStroke

local store = UIService.createStore({
	coins = 500,
	isPurchasing = false,
	errorMessage = nil,
})

local function ShopButton(props)
	local scale, spring, hoverProps = UIService.useScaleButton({
		hoverScale = 1.04,
		pressScale = 0.95,
	})

	return e(TextButton, {
		Size = scale:map(function(value)
			return UDim2.fromOffset(260 * value, 52 * value)
		end),
		BackgroundColor3 = props.BackgroundColor3 or Color3.fromRGB(66, 135, 245),
		Text = props.Text,
		TextColor3 = Color3.fromRGB(255, 255, 255),
		FontFace = Font.fromName("Montserrat"),
		[React.Change.GuiState] = hoverProps[React.Change.GuiState],
		Activated = function()
			spring:setGoal(0.92)
			task.delay(0.06, function()
				spring:setGoal(1)
			end)
			props.Activated()
		end,
	}, {
		Corner = e(UICorner, {
			CornerRadius = UDim.new(0, 10),
		}),
	})
end

local function ShopView()
	local coins = store.use("coins")
	local isPurchasing = store.use("isPurchasing")
	local errorMessage = store.use("errorMessage")

	return e(ScreenGui, {}, {
		Root = e(Frame, {
			AnchorPoint = Vector2.new(0.5, 0.5),
			Position = UDim2.fromScale(0.5, 0.5),
			Size = UDim2.fromScale(0.34, 0.42),
			BackgroundColor3 = Color3.fromRGB(25, 28, 34),
		}, {
			Corner = e(UICorner, {
				CornerRadius = UDim.new(0, 14),
			}),
			Stroke = e(UIStroke, {
				Color = Color3.fromRGB(255, 255, 255),
				Transparency = 0.6,
			}),
			Padding = e(UIPadding, {
				PaddingTop = UDim.new(0, 16),
				PaddingBottom = UDim.new(0, 16),
				PaddingLeft = UDim.new(0, 16),
				PaddingRight = UDim.new(0, 16),
			}),
			Layout = e(UIListLayout, {
				Padding = UDim.new(0, 10),
			}),
			Title = e(TextLabel, {
				Size = UDim2.new(1, 0, 0, 34),
				Text = string.format("Coins: %d", coins),
				TextColor3 = Color3.fromRGB(255, 255, 255),
				TextScaled = false,
				TextSize = 26,
				FontFace = Font.fromName("Montserrat", Enum.FontWeight.Bold),
			}),
			BuySword = e(ShopButton, {
				Text = isPurchasing and "Purchasing..." or "Buy Sword - 200",
				Activated = function()
					if isPurchasing then
						return
					end

					store.batch(function()
						store.set("isPurchasing", true)
						store.set("errorMessage", nil)
					end)

					if coins < 200 then
						store.batch(function()
							store.set("isPurchasing", false)
							store.set("errorMessage", "Not enough coins")
						end)
						return
					end

					task.delay(0.2, function()
						store.batch(function()
							store.set("coins", function(value)
								return value - 200
							end)
							store.set("isPurchasing", false)
						end)
					end)
				end,
			}),
			Error = errorMessage and e(TextLabel, {
				Size = UDim2.new(1, 0, 0, 24),
				Text = errorMessage,
				TextColor3 = Color3.fromRGB(255, 120, 120),
				TextScaled = false,
				TextSize = 18,
			}) or nil,
		}),
	})
end

For a real game, replace that local coin deduction with a server purchase request and update the store from the server response.

Why There Is No Separate Types Module

There used to be a separate Types module during an earlier iteration, but it was removed because the Luau analyzer in this workspace handled those cross-module type imports badly. The public helper types are now exported directly from uiservice itself, which keeps the package simpler and avoids those toolchain issues.

Example: Font And Stroke Defaults

local UIService = require(Packages.uiservice)
local e = UIService.e
local TextLabel = UIService.TextLabel
local UIStroke = UIService.UIStroke

local Label = e(TextLabel, {
	Text = "Hello",
	FontFace = Font.fromName("Montserrat"),
})

local Stroke = e(UIStroke, {
	Color = Color3.fromRGB(255, 255, 255),
})

Notes

  • components now keeps Roblox instance names instead of renaming them.
  • e(TextLabel, { ... }) now applies the built-in default prop set automatically for supported Roblox UI classes.
  • props.TextLabel(), props.TextButton(), and similar helpers are optional now, not required.
  • FontFace support is available through Font.fromName(...), and UIStroke defaults now use StrokeSizingMode = Enum.StrokeSizingMode.Scaled with Thickness = 0.05.
  • components.Menu adds UICorner, UIPadding, and UIListLayout automatically unless you override them in children.
  • More built-in Roblox UI classes now have presets, including TextBox, CanvasGroup, ViewportFrame, VideoFrame, UIGridLayout, UIPageLayout, UISizeConstraint, UIScale, and UIGradient.
  • useSpring, useTween, and useMotion return a React binding plus the underlying Ripple motor.
  • createStore is intentionally small. It helps with UI state fast, without hiding Charm itself.

Recommended Direction

Use uiservice as a thin layer, not a replacement for the original libraries. If a screen needs something more advanced, you can always drop down to React, ReactRoblox, Ripple, or Charm directly because they are still exported.

Package Details

Install command (Click to copy)


Version

0.2.0

License

MIT

check_circle

Safe for commercial use

Automated license review — not legal advice.