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.

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
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.
Refs you call like functions, cached computed values, reactive tables you write as fields, and watchers. No dependency lists, no boilerplate.
Effects, bindings and instances belong to whatever created them and go away with it. No cleanup bookkeeping, no leaks.
Describe UI with Lucy.new, show and each, or hydrate a ScreenGui built in Studio and just add the behaviour.
Springs and tweens bind like any other state. Colours glide through Oklab, and nothing runs while everything is at rest.
Buttons, dialogs, toasts, inputs, panels and more. Copy them into your game with one command, or install the Wally package.
Themes are Universal Styling sheets: light and dark per base colour, switched at runtime, with hover and theme changes costing no Lua.
Write a story for a component or a whole screen, preview it in edit mode, or browse every story with live controls in Play.
Mouse, keyboard, gamepad and touch. Tab and arrows navigate, focus stays inside dialogs, and a themed ring shows only for keys or a gamepad.
Desktop, console, tablet and phone. Adaptive scaling, 44px touch targets, 10-foot sizes and responsive props.
One store drives every component, scene and toast. Title screen, game, menus and modals each own a layer and never steal input.
One package with no runtime dependencies, strict types throughout, and hundreds of specs run in Studio.
Quick start
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" })