In short
Use TweenService:Create(object, TweenInfo.new(time, style, direction), { Property = value }):Play(). For buttons, tween a UIScale inside the button: 1.06 on MouseEnter, 0.94 on MouseButton1Down, back to 1 on leave. For menus, set the UIScale to 0.6, make the frame visible and tween to 1 with the Back easing style for a small overshoot.
- Tween a UIScale, not Size: a UIScale grows the frame and everything inside it, and leaves your layout alone.
- Keep UI tweens short, 0.1 to 0.35 seconds. Longer feels slow.
- Hide a closing panel only when its tween has completed, so it does not vanish mid-animation.
Get the script
UI animation module for buttons and menus
A ModuleScript with hover and press effects for buttons, pop open and close for panels, and a slide-in, all built on TweenService and UIScale.
-- ModuleScript in ReplicatedStorage named UIAnimate.
-- Small tween helpers for menus: hover and press on buttons, pop a panel in and out,
-- slide a panel from off screen. Use it from any LocalScript.
local TweenService = game:GetService("TweenService")
local UIAnimate = {}
local QUICK = TweenInfo.new(0.12, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local POP = TweenInfo.new(0.25, Enum.EasingStyle.Back, Enum.EasingDirection.Out)
local CLOSE = TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
local SLIDE = TweenInfo.new(0.35, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
-- A UIScale grows or shrinks a frame and everything inside it, without changing its Size
local function getScale(object: GuiObject): UIScale
local scale = object:FindFirstChildOfClass("UIScale")
if not scale then
local newScale = Instance.new("UIScale")
newScale.Parent = object
return newScale
end
return scale
end
-- Grow a little on hover, shrink on press, back on release
function UIAnimate.button(button: GuiButton)
local scale = getScale(button)
local hovering = false
local function to(value: number)
TweenService:Create(scale, QUICK, { Scale = value }):Play()
end
button.MouseEnter:Connect(function()
hovering = true
to(1.06)
end)
button.MouseLeave:Connect(function()
hovering = false
to(1)
end)
button.MouseButton1Down:Connect(function()
to(0.94)
end)
button.MouseButton1Up:Connect(function()
to(if hovering then 1.06 else 1)
end)
end
-- Open: show the panel and pop it from small to full size with a slight overshoot
function UIAnimate.open(panel: GuiObject)
local scale = getScale(panel)
scale.Scale = 0.6
panel.Visible = true
TweenService:Create(scale, POP, { Scale = 1 }):Play()
end
-- Close: shrink, then hide
function UIAnimate.close(panel: GuiObject)
local scale = getScale(panel)
local tween = TweenService:Create(scale, CLOSE, { Scale = 0.6 })
tween.Completed:Connect(function(state: Enum.PlaybackState)
if state == Enum.PlaybackState.Completed then
panel.Visible = false
end
end)
tween:Play()
end
-- Slide in from the left edge of the screen to where the panel was placed in Studio
function UIAnimate.slideIn(panel: GuiObject)
local target = panel:GetAttribute("HomePosition")
local home = if typeof(target) == "UDim2" then target else panel.Position
panel:SetAttribute("HomePosition", home)
panel.Position = UDim2.new(-1, 0, home.Y.Scale, home.Y.Offset)
panel.Visible = true
TweenService:Create(panel, SLIDE, { Position = home }):Play()
end
return UIAnimate
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
ReplicatedStorage
UIAnimate ModuleScript new
StarterPlayer
StarterPlayerScripts
AnimateMenus LocalScript new
Settings you can change
POPIn the script- The opening animation: a quarter second with a small overshoot. QUICK, CLOSE and SLIDE beside it set the others. Default:
TweenInfo.new(0.25, Enum.EasingStyle.Back, Enum.EasingDirection.Out).
Use it with the AnimateMenus LocalScript from Step 2, which gives every button the effect and opens panels from buttons with an Opens attribute.
Type-checked with luau-lsp strict against Roblox's API definitions on 23 October 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.
Prompt for RoCode
Animate my game's UI: a UIAnimate ModuleScript in ReplicatedStorage with TweenService helpers for button hover and press using UIScale, pop open and close for panels, and slide in, plus a LocalScript in StarterPlayerScripts that applies the button effect to every GuiButton and lets buttons with an Opens attribute open and close the frame with that name.
How UI tweens work
A tween moves a property from its current value to a target over time. TweenInfo sets how long it takes and how it moves: the easing style is the shape of the motion and the direction says whether it eases at the start, the end or both.
| Style | Feels like | Use for |
|---|---|---|
Quad, Out | Fast start, gentle stop | Hover and press |
Back, Out | Goes slightly past, then settles | Opening a panel |
Quint, Out | Very fast, then a long glide | Sliding in from the side |
Linear | Constant speed | Progress bars, spinning icons |
Step 1: The animation module
Insert a ModuleScript in ReplicatedStorage named UIAnimate:
-- ModuleScript in ReplicatedStorage named UIAnimate.
-- Small tween helpers for menus: hover and press on buttons, pop a panel in and out,
-- slide a panel from off screen. Use it from any LocalScript.
local TweenService = game:GetService("TweenService")
local UIAnimate = {}
local QUICK = TweenInfo.new(0.12, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local POP = TweenInfo.new(0.25, Enum.EasingStyle.Back, Enum.EasingDirection.Out)
local CLOSE = TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
local SLIDE = TweenInfo.new(0.35, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
-- A UIScale grows or shrinks a frame and everything inside it, without changing its Size
local function getScale(object: GuiObject): UIScale
local scale = object:FindFirstChildOfClass("UIScale")
if not scale then
local newScale = Instance.new("UIScale")
newScale.Parent = object
return newScale
end
return scale
end
-- Grow a little on hover, shrink on press, back on release
function UIAnimate.button(button: GuiButton)
local scale = getScale(button)
local hovering = false
local function to(value: number)
TweenService:Create(scale, QUICK, { Scale = value }):Play()
end
button.MouseEnter:Connect(function()
hovering = true
to(1.06)
end)
button.MouseLeave:Connect(function()
hovering = false
to(1)
end)
button.MouseButton1Down:Connect(function()
to(0.94)
end)
button.MouseButton1Up:Connect(function()
to(if hovering then 1.06 else 1)
end)
end
-- Open: show the panel and pop it from small to full size with a slight overshoot
function UIAnimate.open(panel: GuiObject)
local scale = getScale(panel)
scale.Scale = 0.6
panel.Visible = true
TweenService:Create(scale, POP, { Scale = 1 }):Play()
end
-- Close: shrink, then hide
function UIAnimate.close(panel: GuiObject)
local scale = getScale(panel)
local tween = TweenService:Create(scale, CLOSE, { Scale = 0.6 })
tween.Completed:Connect(function(state: Enum.PlaybackState)
if state == Enum.PlaybackState.Completed then
panel.Visible = false
end
end)
tween:Play()
end
-- Slide in from the left edge of the screen to where the panel was placed in Studio
function UIAnimate.slideIn(panel: GuiObject)
local target = panel:GetAttribute("HomePosition")
local home = if typeof(target) == "UDim2" then target else panel.Position
panel:SetAttribute("HomePosition", home)
panel.Position = UDim2.new(-1, 0, home.Y.Scale, home.Y.Offset)
panel.Visible = true
TweenService:Create(panel, SLIDE, { Position = home }):Play()
end
return UIAnimate
getScale finds or adds a UIScale. button connects hover and press. open and close pop a panel; close hides it only when the tween reached the end (PlaybackState.Completed), so opening again mid-close does not get hidden by the old tween. slideIn remembers where you placed the panel in a HomePosition attribute, moves it off the left edge and tweens it back.
Step 2: Apply it to every button
Insert a LocalScript in StarterPlayer > StarterPlayerScripts named AnimateMenus. It runs once per player, and gives every button in their ScreenGuis the hover and press effect, including buttons added later, such as GUIs that are copied in again after a respawn:
-- LocalScript in StarterPlayerScripts named AnimateMenus.
-- Gives every button in your ScreenGuis the hover and press animation, and makes
-- buttons with an "Opens" attribute open and close the frame with that name.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
type UIAnimate = {
button: (GuiButton) -> (),
open: (GuiObject) -> (),
close: (GuiObject) -> (),
slideIn: (GuiObject) -> (),
}
local UIAnimate = require(ReplicatedStorage:WaitForChild("UIAnimate")) :: UIAnimate
local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
local done: { [Instance]: boolean } = {}
local function setUp(object: Instance)
if done[object] or not object:IsA("GuiButton") then
return
end
done[object] = true
UIAnimate.button(object)
-- Opens = "ShopFrame": this button opens or closes the frame named ShopFrame in the same ScreenGui
local opens = object:GetAttribute("Opens")
local screen = object:FindFirstAncestorOfClass("ScreenGui")
if typeof(opens) == "string" and screen then
object.Activated:Connect(function()
local panel = screen:FindFirstChild(opens, true)
if panel and panel:IsA("GuiObject") then
if panel.Visible then
UIAnimate.close(panel)
else
UIAnimate.open(panel)
end
end
end)
end
end
for _, object in playerGui:GetDescendants() do
setUp(object)
end
playerGui.DescendantAdded:Connect(setUp)
- For a button that opens a menu, add a String attribute named
Opensto the button, with the name of the frame, such asShopFrame. - Put that frame in the same ScreenGui and untick its Visible.
- Press Play and click the button: the shop pops open. Click again: it closes.
The script requires the module with a type annotation, so strict type checking knows its functions. From any other LocalScript, call UIAnimate.slideIn(frame) or UIAnimate.open(frame) the same way.
Test it
- Press Play and hover over any button: it grows slightly. Press and hold: it shrinks. Release: it grows back.
- Click a button with an Opens attribute: its frame pops open with a small bounce, and closes on the next click.
- Test in the Device Simulator with a phone: taps press the button; there is no hover on touch screens.
Common errors and fixes
If nothing animates, check the Output window first.
Buttons do not animate
AnimateMenus must be a LocalScript in StarterPlayerScripts, and UIAnimate a ModuleScript in ReplicatedStorage with that exact name.
The panel jumps instead of growing
Something else sets its Size every frame, or it has a UISizeConstraint that fights the UIScale. Animate the UIScale only.
The Opens button does nothing
The attribute must be a String with the exact name of a GuiObject in the same ScreenGui.
Text looks blurry while it grows
That is normal mid-tween. Keep scales between about 0.9 and 1.1 for buttons.
Build it with RoCode
If you would rather not set this up by hand, RoCode can build it 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.
Make every button in my game grow on hover and shrink on click, and make the shop and settings panels pop open and closed.
- Can insert ready-made, animated screens from its premium UI kits in one step: HUD, pet inventory, store, daily rewards, item shop, egg hatching, rebirth, settings, daily quests and a loading screen, in five themes (candy, neon, gold, blocky, pastel), with working server scripts
- Or adds the animation module from this guide to the GUIs you already have
- Is told to compile-check each script with Check Script, and sets undo points around each batch
After a build, RoCode can run its test robot: it plays your game in Studio, clicks buttons and reports PASS or FAIL. It checks only what it chose to test, so judge the feel yourself. How RoCode connects to Studio.
Questions
How do I tween UI in Roblox?
TweenService:Create(guiObject, TweenInfo.new(0.25), { Position = UDim2.fromScale(0.5, 0.5) }):Play() moves a frame. Any number, UDim2 or Color3 property can be tweened.
How do I make a button grow when hovered?
Put a UIScale in the button and tween its Scale to about 1.06 on MouseEnter and back to 1 on MouseLeave.
Should I tween Size or use UIScale?
UIScale. It scales the frame and all its children together and does not change the Size you set in Studio.
Why does my closing animation get cut off?
The frame was hidden before the tween ended. Hide it in the tween's Completed event.
Sources and further reading
- Roblox Creator Docs: UI animation. tweening GUI properties and easing
- Roblox Creator Docs: TweenService. Create and Play
- Roblox Creator Docs: TweenInfo. time, easing style and direction
- Roblox Creator Docs: UIScale. scaling a GUI and its children
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 23 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.