Tutorial · Scripting foundations

TweenService tutorial: move, rotate and fade parts

A tween changes a property smoothly from its current value to a goal over time: a platform slides, a gem spins, a wall fades out. TweenService does the in-between steps for you, so you never write a loop that nudges a part every frame. This guide covers the three lines every tween needs, what each TweenInfo setting does, moving, rotating and fading parts, loops, waiting for a tween to finish, and the trick for tweening a whole Model.

Updated 5 November 20267 min readBeginnerLuauWritten by

In short

Get the service with game:GetService("TweenService"), describe the motion with TweenInfo.new(time, easingStyle, easingDirection), then call TweenService:Create(part, info, { Position = goal }) and :Play() the tween it returns. Any number, Vector3, CFrame, Color3, UDim2 or similar property can be tweened. Use tween.Completed:Wait() to wait for it, and give TweenInfo a repeat count of -1 with reverses on for a back-and-forth loop.

  • Tween the part on the server when every player should see it; on the client for effects only that player needs.
  • Anchor parts you tween, or physics fights the tween.
  • A Model has no Position or CFrame to tween: tween a NumberValue and move the model with PivotTo.

Get the script

Spinning and floating part script (TweenService)

A Script that makes a part bob up and down and spin forever with two looping tweens, for coins, gems and pickups.

Script in a Part in Workspace named FloatingGem
-- Script inside a Part named Gem. Floats up and down and spins forever.
local TweenService = game:GetService("TweenService")

local FLOAT_HEIGHT = 1.5 -- studs above where you placed it
local FLOAT_SECONDS = 1.2 -- each way
local SPIN_SECONDS = 3 -- one full turn

local gem = script.Parent :: BasePart
gem.Anchored = true
local baseCFrame = gem.CFrame

local height = Instance.new("NumberValue")
local angle = Instance.new("NumberValue")

local function apply()
	gem.CFrame = baseCFrame * CFrame.new(0, height.Value, 0) * CFrame.Angles(0, math.rad(angle.Value), 0)
end
height.Changed:Connect(apply)
angle.Changed:Connect(apply)

TweenService:Create(height, TweenInfo.new(FLOAT_SECONDS, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true), {
	Value = FLOAT_HEIGHT,
}):Play()

TweenService:Create(angle, TweenInfo.new(SPIN_SECONDS, Enum.EasingStyle.Linear, Enum.EasingDirection.In, -1), {
	Value = 360,
}):Play()

RoCode builds it in the place you have open in Roblox Studio, on Windows or macOS with a desktop browser; on a phone the button sends the prompt and the link to your computer. Free to start, with free credits every day. More Roblox Studio scripts

Where to put it

Explorer: the Script inside the part
Workspace
  Gem Part new
    FloatingGem Script new

Settings you can change

FLOAT_HEIGHTIn the script
How many studs the part rises above where you placed it. Default: 1.5.
SPIN_SECONDSIn the script
Seconds for one full turn. Default: 3.

Tick Anchored on the part. For many gems, use one script and tags as the CollectionService guide shows.

Type-checked with luau-lsp strict against Roblox's API definitions on 5 November 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.

Prompt for RoCode

Make every part tagged Gem float up and down by 1.5 studs and spin slowly forever using TweenService, from one Script in ServerScriptService.

On this page
  1. Get the script
  2. How it works
  3. 1. TweenInfo
  4. 2. Rotate and fade
  5. 3. Loops
  6. 4. Models
  7. Server or client
  8. Common errors
  9. With RoCode
  10. Questions
  11. Sources

The three lines every tween needs

Insert a Part in Workspace, tick Anchored, then hover over it, click + and insert a Script. This moves the part 10 studs up in 2 seconds:

Script in a Part in Workspace named Rise
-- Script inside a Part.
local TweenService = game:GetService("TweenService")

local part = script.Parent :: BasePart

local info = TweenInfo.new(2) -- 2 seconds, default easing
local goal = { Position = part.Position + Vector3.new(0, 10, 0) }

local tween = TweenService:Create(part, info, goal)
tween:Play()

The goal is a table of property names and the values they should end at. You can tween several properties at once: { Position = ..., Transparency = 1, Color = Color3.fromRGB(255, 0, 0) }. The tween starts from whatever the values are when you call Play, so you only ever give the end.

What can and cannot be tweened
TypeTweens smoothly?Examples
numberYesTransparency, Reflectance, a NumberValue's Value
Vector3, CFrameYesPosition, Size, CFrame, Orientation
Color3YesColor
UDim2, Vector2YesGUI Size and Position
boolean, EnumItemSwitches at the endCanCollide, Material
string, InstanceNoGives cannot be tweened due to type mismatch

Step 1: Every TweenInfo setting

TweenInfo.new takes up to six values, in this order. You can leave off the ones at the end.

TweenInfo.new(time, style, direction, repeatCount, reverses, delayTime)
ValueDefaultWhat it does
time1Seconds for one play.
easingStyleQuadThe shape of the motion: Linear (constant speed), Sine, Quad, Cubic, Back (overshoots), Bounce, Elastic, Exponential.
easingDirectionOutOut starts fast and slows down; In starts slow; InOut does both.
repeatCount0Extra plays after the first. -1 repeats forever.
reversesfalsePlay back to the start after each play.
delayTime0Seconds to wait before starting.
Script in a Part in Workspace named Platform
-- Script inside a Part named Platform.
-- Slides back and forth 12 studs forever, easing at both ends.
local TweenService = game:GetService("TweenService")

local platform = script.Parent :: BasePart

local info = TweenInfo.new(
	3, -- seconds each way
	Enum.EasingStyle.Sine,
	Enum.EasingDirection.InOut,
	-1, -- repeat forever
	true -- come back each time
)

TweenService:Create(platform, info, { Position = platform.Position + Vector3.new(12, 0, 0) }):Play()

For a platform that should carry players standing on it, a tweened anchored part moves under them but does not take them along. The moving platform guide covers that case.

Step 2: Rotate, resize and fade

To rotate, tween CFrame to the current CFrame times a rotation. Tweening Orientation also works, but CFrame keeps the part in place while it turns. Here a wall fades and shrinks when the script runs, then is removed once the tween finishes:

Script in a Part in Workspace named FadingWall
-- Script inside a Part named FadingWall.
local TweenService = game:GetService("TweenService")

local wall = script.Parent :: BasePart

-- turn a quarter around the part's own up axis in 1 second
local turn = TweenService:Create(wall, TweenInfo.new(1), {
	CFrame = wall.CFrame * CFrame.Angles(0, math.rad(90), 0),
})
turn:Play()
turn.Completed:Wait() -- carry on only once it has finished

-- then fade out and shrink at the same time
local fade = TweenService:Create(wall, TweenInfo.new(0.8, Enum.EasingStyle.Quad, Enum.EasingDirection.In), {
	Transparency = 1,
	Size = wall.Size * 0.2,
})
fade:Play()
fade.Completed:Wait()
wall:Destroy()

Completed fires with a PlaybackState: Completed when it reached the goal, or Cancelled when tween:Cancel() stopped it. Do not wait on Completed for a tween that repeats forever: it never finishes.

Step 3: A floating, spinning pickup

Two looping tweens on different properties run side by side. Tweening a full 360 degrees with CFrame does nothing, because the start and the end are the same rotation, so the spin tweens a NumberValue from 0 to 360 and sets the CFrame from it. Put this Script inside an anchored part:

Script in a Part in Workspace named FloatingGem
-- Script inside a Part named Gem. Floats up and down and spins forever.
local TweenService = game:GetService("TweenService")

local FLOAT_HEIGHT = 1.5 -- studs above where you placed it
local FLOAT_SECONDS = 1.2 -- each way
local SPIN_SECONDS = 3 -- one full turn

local gem = script.Parent :: BasePart
gem.Anchored = true
local baseCFrame = gem.CFrame

local height = Instance.new("NumberValue")
local angle = Instance.new("NumberValue")

local function apply()
	gem.CFrame = baseCFrame * CFrame.new(0, height.Value, 0) * CFrame.Angles(0, math.rad(angle.Value), 0)
end
height.Changed:Connect(apply)
angle.Changed:Connect(apply)

TweenService:Create(height, TweenInfo.new(FLOAT_SECONDS, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true), {
	Value = FLOAT_HEIGHT,
}):Play()

TweenService:Create(angle, TweenInfo.new(SPIN_SECONDS, Enum.EasingStyle.Linear, Enum.EasingDirection.In, -1), {
	Value = 360,
}):Play()

The spin uses Linear easing so it turns at a steady speed, and it restarts at 0 each loop, which is the same angle as 360, so there is no jump. The coin collect guide puts pickups like this into a game.

Step 4: Tween a whole Model

A Model has no Position or CFrame property, so TweenService:Create(model, ...) has nothing to move. Tween a NumberValue from 0 to 1 instead and move the model with PivotTo, which moves every part inside it together:

Script in a Model in Workspace named Elevator
-- Script inside a Model named Elevator.
-- Moves the whole model 20 studs up and back, every part together.
local TweenService = game:GetService("TweenService")

local elevator = script.Parent :: Model
local startPivot = elevator:GetPivot()
local endPivot = startPivot * CFrame.new(0, 20, 0)

local progress = Instance.new("NumberValue")
progress.Changed:Connect(function(alpha: number)
	elevator:PivotTo(startPivot:Lerp(endPivot, alpha))
end)

local info = TweenInfo.new(4, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, -1, true, 2)
TweenService:Create(progress, info, { Value = 1 }):Play()

Anchor every part of the model. The 2 at the end of TweenInfo is the delay before each play, so the elevator waits at the top and the bottom. The door guide uses the same pattern to swing doors made of several parts.

Server or client?

A tween played in a server Script changes the part on the server, and the new values replicate to every player, so everyone sees it. A tween in a LocalScript changes the part only on that player's screen.

Where to play a tween
Play it inWhenExample
A server ScriptThe change matters to the game or to everyone.Doors, moving obstacles, a bridge that opens
A LocalScriptOnly this player should see it, or it is purely a visual effect.A pickup fading for the player who collected it, UI

Replication sends updates many times a second, so server tweens look smooth to most players, but very fast or very many server tweens can look less smooth on slow connections. For lots of decorative motion (hundreds of spinning coins), play the tweens in a LocalScript on each client.

Common errors and fixes

Open the Output window (Window menu, then Output) before you press Play.

The part does not move at all

Check that you called :Play() on the tween, that the goal property name is spelled exactly (Position, not position), and that it is a Script in a place where scripts run (inside a part in Workspace, or ServerScriptService).

The part falls or shakes while tweening

It is not anchored, so gravity and collisions fight the tween. Tick Anchored.

TweenService:Create property cannot be tweened due to type mismatch

The goal value is the wrong type, for example a number for a Vector3 property. See the error page.

Unable to cast to Dictionary or Unable to cast value to Object

The arguments are in the wrong order or the wrong type. It is always Create(instance, tweenInfo, goalTable). See Unable to cast value to Object.

Build it with RoCode

If you would rather not write tweens by hand, RoCode can build them in the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the scripts in your Explorer, so there is no code to paste.

You type

Make the bridge model in Workspace slide 15 studs to the side when the round starts, using a NumberValue tween and PivotTo, and fade the red barrier parts out over 1 second at the same time.

RoCode does
  • Can find the parts and models you mean with Search Game before it writes anything
  • Writes the Script with Create Script and is told to compile-check it with Check Script
  • Sets an undo point before and after each batch of changes, so Ctrl+Z steps back one batch at a time

With plugin 3.9 or newer RoCode can run a short playtest and read the errors, but motion is easiest to judge with your own eyes, so press Play and watch it. How RoCode connects to Studio.

Questions

How do I use TweenService in Roblox?

Get the service, make a TweenInfo, call TweenService:Create(object, info, { Property = goal }) and :Play() the result. The first section has a three-line example.

How do I make a tween loop forever?

Pass -1 as the fourth value of TweenInfo.new (repeatCount), and true as the fifth if it should go back and forth.

How do I wait for a tween to finish?

Call tween.Completed:Wait() after tween:Play(), or connect a function to tween.Completed.

Can I tween a Model?

Not directly. Tween a NumberValue from 0 to 1 and call model:PivotTo(start:Lerp(goal, value)) when it changes, as in Step 4.

How do I stop a tween?

Call tween:Cancel() to stop and leave the part where it is, or tween:Pause() to resume later with Play().

Sources and further reading

How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 5 November 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

Try RoCode on your own place.

Free every day, no card required. Install the plugin, describe the feature, and review what lands in Studio.