Skip to content

A UI kit for Roblox

Fine-grained reactive state, declarative UI and themeable components, with first-class mouse, keyboard, gamepad and touch on desktop, console, tablet and phone.

$ lune run ~/Projects/Lucy/scripts/add -- button dialog --out src/shared/Lucy

Features

Everything a game UI needs, out of the box

Fine-grained by design

Change a value and only the properties that read it update. Nothing re-renders and nothing is diffed: editing one row of a 1,000-row list takes about 5 µs.

State that reads like Luau

Refs you call like functions, cached computed values, reactive tables you write as fields, and watchers. No dependency lists, no boilerplate.

Cleans up after itself

Effects, bindings and instances belong to whatever created them and go away with it. No cleanup bookkeeping, no leaks.

Declarative, or built in Studio

Describe UI with Lucy.new, show and each, or hydrate a ScreenGui built in Studio and just add the behaviour.

Motion is a value

Springs and tweens bind like any other state. Colours glide through Oklab, and nothing runs while everything is at rest.

Components you own

Buttons, dialogs, toasts, inputs, panels and more. Copy them into your game with one command, or install the Wally package.

Styling runs in the engine

Themes are Universal Styling sheets: light and dark per base colour, switched at runtime, with hover and theme changes costing no Lua.

See it in Studio

Write a story for a component or a whole screen, preview it in edit mode, or browse every story with live controls in Play.

Every input, focus included

Mouse, keyboard, gamepad and touch. Tab and arrows navigate, focus stays inside dialogs, and a themed ring shows only for keys or a gamepad.

Every screen

Desktop, console, tablet and phone. Adaptive scaling, 44px touch targets, 10-foot sizes and responsive props.

One app, clean scenes

One store drives every component, scene and toast. Title screen, game, menus and modals each own a layer and never steal input.

Light and dependable

One package with no runtime dependencies, strict types throughout, and hundreds of specs run in Studio.

Quick start

A title screen in a dozen lines

UI.client.luau
local Lucy = require(ReplicatedStorage.Shared.Lucy)
local app = Lucy.App.new({ theme = "dark", baseColor = "zinc", primary = "violet" })

app.scenes:register("Title", {
	build = function(scene)
		scene.play = Lucy.Button.new({
			id = "play", text = "Play", size = "lg", Parent = scene.gui,
			onClick = function() app.scenes:goTo("Game") end,
		})
	end,
	defaultFocus = function(scene) return scene.play end,
})
app.scenes:goTo("Title")

app.state:set("ui.play.disabled", true) -- drive any component from anywhere
app.toast.success("Welcome back", { description = "Daily reward ready" })