Skip to content

Home › Motion

Tween ​

Flux.tween(target, tweenInfo?) returns a reactive node that animates toward its target over a fixed duration and easing curve, reusing Roblox's TweenInfo without imperative Tween:Play() / Tween:Cancel() boilerplate.

luau
local loadingProgress = Flux.tween(0, TweenInfo.new(1, Enum.EasingStyle.Linear))

local progressBar = Flux.new "Frame" {
    BackgroundColor3 = Color3.fromRGB(0, 255, 100),
    Size = function()
        return UDim2.fromScale(loadingProgress(), 1)
    end,
}

-- Smoothly tween toward 1 (100%) over 1 second
loadingProgress(1)

Basic Usage ​

Flux.tween takes two arguments:

  1. target: The value (or reactive source) the tween animates toward.
  2. tweenInfo (optional): A Roblox TweenInfo controlling duration, easing style, direction, repeat count, reverses, and delay. Defaults to 0.3 seconds with Cubic InOut easing.

The returned node works just like a Signal. Read it with tween(), retarget it with tween(newGoal) or :set(newGoal), and bind it directly to properties.

Reactive Targets ​

Pass a Signal, Computed, or plain function () -> T as target. Whenever the source updates, the tween smoothly redirects from its current in-flight value toward the new goal:

luau
local isHovering = Flux.signal(false)

local buttonColor = Flux.tween(
    function()
        return if isHovering() then Color3.fromRGB(80, 80, 80) else Color3.fromRGB(45, 45, 45)
    end,
    TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
)

local myButton = Flux.new "TextButton" {
    Size = UDim2.fromOffset(200, 50),
    BackgroundColor3 = buttonColor,
    MouseEnter = function() isHovering(true) end,
    MouseLeave = function() isHovering(false) end,
}

Reactive TweenInfo ​

tweenInfo can also be a reactive node or function. When it updates, the in-flight animation continues from its current position using the new duration and easing parameters:

luau
local isFastMode = Flux.signal(false)
local targetPosition = Flux.signal(UDim2.fromScale(0, 0))

local slidingPosition = Flux.tween(targetPosition, function()
    return if isFastMode()
        then TweenInfo.new(0.1, Enum.EasingStyle.Linear)
        else TweenInfo.new(0.5, Enum.EasingStyle.Exponential)
end)

Lifecycle ​

When created inside a Scope, a tween's per-frame step registration is cleaned up automatically when the scope is destroyed. To stop and dispose a tween early, call tween:Destroy().

A Bound Tween Is Not Owned by Its Instance

Flux.new and Flux.edit own a getter function bound to a property (Size = function() ... end), but a tween passed straight to a property (Size = Flux.tween(fn)) only gets a binding. Created outside any owner, a tween with a reactive target or tweenInfo keeps its driving Effect subscribed to those sources after the instance is destroyed, and re-runs it whenever they change. Create the tween inside a Scope that is destroyed with the instance, or hand it to the instance's __CLEAN:

luau
local width = Flux.tween(function()
    return if isOpen() then UDim2.fromOffset(300, 40) else UDim2.fromOffset(0, 40)
end)

local panel = Flux.new "Frame" {
    Size = width,
    __CLEAN = { width }, -- destroying `panel` destroys the tween and its Effect
}

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:

Passing an unsupported type, such as a boolean, string, or EnumItem, raises an error. The one exception is nil: a motion that starts at nil warns and then follows its target without animating.

Oklab Color Interpolation ​

When animating a Color3, ColorSequence, or ColorSequenceKeypoint, Flux converts colors into the Oklab perceptual color space instead of interpolating linear RGB, avoiding muddy or grayish midpoints.

The same perceptual space is available as a standalone toolkit (lighten, saturate, blend, and check contrast) on the Color page.