Skip to content

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:

bash
rojo sourcemap --output sourcemap.json

Rojo, Selene & StyLua ​

  • Rojo syncs external .luau files 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.new trees 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:

luau
-- 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 Counter

Hoarcekat (Universal Function Format) ​

A bare function(target) story works in Hoarcekat, UI Labs, and Flipbook:

luau
-- 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
end

UI Labs (With Live Controls) ​

UI Labs mounts once and streams control edits through props.subscribe, which you can forward into a Flux signal:

luau
-- 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:

luau
-- 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,
}