Tween
Springs suit physics-driven interactions, but sometimes you need an animation to take a specific amount of time, or follow a specific easing curve.
Roblox handles this with TweenService, but imperative Tween:Play() and Tween:Cancel() calls don't mix well with a declarative UI library. Flux provides a reactive tweening primitive that reuses Roblox's easing curves, so you can bind it directly to your UI properties like a standard Signal.
Basic Usage
You create a reactive tween using Flux.tween().
The function takes two arguments:
- Target: The value the tween should animate toward.
- TweenInfo (Optional): A standard Roblox
TweenInfoobject that dictates the duration, easing style, and easing direction. If omitted, it defaults to0.3seconds withCubicInOuteasing.
The resulting object acts like a standard Flux Signal, so it can be bound directly to UI properties.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Flux = require(ReplicatedStorage.Flux)
local new = Flux.new
-- Create a tween starting at 0, using a 1-second Linear tween
local loadingProgress = Flux.tween(
0,
TweenInfo.new(1, Enum.EasingStyle.Linear)
)
local progressBar = new "Frame" {
BackgroundColor3 = Color3.fromRGB(0, 255, 100),
-- Bind the tweened value directly to the size
Size = function()
return UDim2.fromScale(loadingProgress(), 1)
end
}
-- Later: Update the target to 1 (100%). It will tween there over 1 second.
loadingProgress(1)Reactive Targets
Like Springs, Flux Tweens are part of the reactive graph. If you pass an existing Flux Signal or Computed into the Flux.tween constructor, the tween tracks it.
Whenever that underlying state changes, the tween begins animating toward the new target from its current position.
local isHovering = Flux(false)
-- The target is a Computed based on hover state
local buttonColor = Flux.tween(
Flux(function()
return isHovering() and Color3.fromRGB(80, 80, 80) or Color3.fromRGB(45, 45, 45)
end),
TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
)
local myButton = new "TextButton" {
Size = UDim2.fromOffset(200, 50),
-- Bind the reactive tween
BackgroundColor3 = buttonColor,
-- Mutate the underlying state
MouseEnter = function() isHovering(true) end,
MouseLeave = function() isHovering(false) end
}Reactive TweenInfo
In highly dynamic systems, you might want the animation duration or easing style to change based on the state of the game. Flux supports passing a reactive node for the TweenInfo argument as well.
When the underlying TweenInfo node updates, the change takes effect immediately: the in-flight animation continues from its current position using the new duration and easing parameters.
local isFastMode = Flux(false)
local targetPosition = Flux(UDim2.fromScale(0, 0))
local dynamicTweenInfo = Flux(function()
if isFastMode() then
return TweenInfo.new(0.1, Enum.EasingStyle.Linear)
else
return TweenInfo.new(0.5, Enum.EasingStyle.Exponential)
end
end)
-- The tween tracks `targetPosition` and adapts its speed based on `isFastMode`
local slidingPosition = Flux.tween(targetPosition, dynamicTweenInfo)
-- Updating either node animates `slidingPosition` toward the new goal
isFastMode(true)
targetPosition(UDim2.fromScale(1, 0))Lifecycle
A tween's node is a plain signal, so the scope it was created in (usually the component that built it) does not own it; the GC reclaims it once unreferenced. The tween's per-frame engine registration is tied to that scope: when the scope is destroyed the tween stops animating and unregisters from the engine automatically, so tweens built into your UI need no manual cleanup.
To tear one down early, call tween:Destroy(): it stops the animation, removes it from the engine's stepped set, and disposes the underlying node.
Supported Data Types
Flux interpolates almost all standard Roblox UI and 3D data types directly, so you never need to split out X, Y, and Z components or calculate alphas manually.
Supported types include:
numberUDim&UDim2Vector2,Vector3,Vector2int16,Vector3int16Color3CFrameNumberRange,NumberSequenceKeypoint,ColorSequenceKeypointRect,Ray,PhysicalProperties,DateTimeRegion3,Region3int16
NOTE
Passing an unsupported type, such as a boolean, string, EnumItem, or a whole ColorSequence / NumberSequence (animate their individual keypoints instead), raises an error. The one exception is nil: a motion that starts at nil warns and then simply follows its target without animating.
🎨 Oklab Color Interpolation
When animating a Color3 (or the color of a ColorSequenceKeypoint), Flux bypasses standard linear RGB interpolation, which often produces muddy or grayish intermediate colors. Instead, colors are converted into the Oklab perceptual color space for the duration of the animation, producing vibrant, naturally blended transitions that match how the human eye perceives light.
The same perceptual space is available as a standalone toolkit (lighten, saturate, blend, build from temperature, and check contrast) on the Color page.