Developer Tools
Flux works out of the box in Roblox Studio and pairs cleanly with external Luau tooling for type checking, linting, headless testing, and UI storybooks.
Editor & Static Analysis
Luau Language Server (luau-lsp)
luau-lsp powers autocomplete, go-to-definition, and inline type diagnostics in VS Code, Cursor, and Neovim. Because Flux is written in --!strict mode with typed class constructors and State<T> / Node<T> inference, luau-lsp gives you exact property and event autocomplete inside Flux.new and Flux.edit.
Generate a Rojo sourcemap so the language server can resolve module paths:
rojo sourcemap --output sourcemap.jsonRojo, Selene & StyLua
- Rojo syncs external
.luaufiles into Roblox Studio so you can use Git and external editors. - Selene catches shadowed variables, unused locals, and suspicious Luau constructs.
- StyLua formats declarative
Flux.newtrees and reactive bindings consistently across your team.
Headless & Virtual-Time Testing
Flux's reactive graph and Flux.Clock run under the standalone luau CLI with zero Roblox dependencies. You can step timers, springs, tweens, and frame flushes deterministically in unit tests without launching Studio. See Testing for details.
UI Previewing (Storybooks)
Roblox storybook plugins mount a component into a target container and return a cleanup callback. You can wrap the component in Flux.scope and return scope:Destroy().
Given a reusable Counter component:
-- Counter.luau
local Flux = require(ReplicatedStorage.Flux)
local function Counter(step: Flux.Reactive<number>?)
local count = Flux.signal(0)
return Flux.new "TextButton" {
Size = UDim2.fromOffset(200, 50),
Text = function()
return `Clicks: {count()}`
end,
Activated = function()
count(count() + (Flux.read(step) or 1))
end,
}
end
return CounterHoarcekat (Universal Function Format)
A bare function(target) story works in Hoarcekat, UI Labs, and Flipbook:
-- Counter.story.luau
local Flux = require(ReplicatedStorage.Flux)
local Counter = require(script.Parent.Counter)
return function(target: Instance)
local scope, root = Flux.scope(Counter)
root.Parent = target
return function()
scope:Destroy()
end
endUI Labs (With Live Controls)
UI Labs mounts once and streams control edits through props.subscribe, which you can forward into a Flux signal:
-- Counter.story.luau
local Flux = require(ReplicatedStorage.Flux)
local Counter = require(script.Parent.Counter)
return {
controls = { step = 1 },
render = function(props)
local step = Flux.signal(props.controls.step)
props.subscribe(function(values)
step(values.step)
end)
local scope, root = Flux.scope(function()
return Counter(step)
end)
root.Parent = props.target
return function()
scope:Destroy()
end
end,
}Flipbook (With Controls)
Flipbook re-runs story when a control changes:
-- Counter.story.luau
local Flux = require(ReplicatedStorage.Flux)
local Counter = require(script.Parent.Counter)
return {
summary = "A reactive counter",
controls = { step = 1 },
story = function(props)
local scope, root = Flux.scope(function()
return Counter(props.controls.step)
end)
root.Parent = props.container
return function()
scope:Destroy()
end
end,
}