A modular, high-performance UI hover effects library for Roblox.
Add polished, modern animations to any GUI element in seconds — glow, ripple, parallax, magnetic, spring physics and more, By Jake Nelson.
Installation · Quick Start · All Presets · Options · Styles · API · Examples
HoverFX is a plug-and-play hover effect system for Roblox UI. Instead of writing repetitive TweenService boilerplate for every button, you call one line:
HoverFX.attach(button, "LiftScale")Every effect is a self-contained preset module — you can stack multiple on the same element, detach them at runtime, define reusable style bundles, or bulk-apply to an entire ScreenGui.
HoverFX (ModuleScript)
├── Elements/ — empty folder for user extensions
├── Features/ — 19 individual effect modules
│ ├── Mobile — touch hover fallback
│ ├── Press — click press-down feedback
│ ├── Scale — scale up on hover
│ ├── Shrink — scale down on hover
│ ├── Lift — vertical lift on hover
│ ├── LiftScale — lift + scale (most popular)
│ ├── ShadowDepth — animated drop shadow
│ ├── Glow — UIStroke border glow
│ ├── Underline — animated underline reveal
│ ├── ColorShift — background/text color lighten
│ ├── GradientSlide — UIGradient rotation + offset
│ ├── ShineSweep — highlight sweep across element
│ ├── RippleClick — material ripple from click origin
│ ├── Sound — hover and click sounds
│ ├── ParallaxFollow — 3D cursor-following tilt
│ ├── MagneticHover — proximity magnetic pull
│ ├── IconWiggle — child icon wiggle animation
│ ├── PulseGlow — pulsing glow aura
│ └── Wave — horizontal stretch-snap wave
├── Packages/
│ └── Spring — Heartbeat physics engine
├── Attribute — runtime disable helpers
├── Reference — weak-key state registry
├── Types — HoverOptions type exports
├── Utility — defaults, helpers, factories
└── VERSION — returns "1.0.0"
- Download
HoverFX.rbxmxfrom Releases - Drag and drop the file into Roblox Studio — it will insert as a
ModuleScript - Move
HoverFXintoReplicatedStorage
Clone this repo and sync with Rojo into your project.
local HoverFX = require(game.ReplicatedStorage.HoverFX)local HoverFX = require(game.ReplicatedStorage.HoverFX)
-- Single effect
HoverFX.attach(script.Parent, "LiftScale")
-- Full premium pack in one call
HoverFX.quick(script.Parent)
-- Clean up when done
HoverFX.detach(script.Parent)TextButton
└── LocalScript
local HoverFX = require(game.ReplicatedStorage.HoverFX)
local button = script.Parent
HoverFX.attach(button, "LiftScale", { Scale = 1.1, Lift = 8 })
HoverFX.attach(button, "RippleClick")
HoverFX.attach(button, "Glow", { GlowColor = Color3.fromRGB(255, 200, 80) })All presets are called with
HoverFX.attach(element, "PresetName", options?)
Scales up and lifts upward on hover. The go-to preset for buttons.
HoverFX.attach(button, "LiftScale")
HoverFX.attach(button, "LiftScale", { Scale = 1.1, Lift = 10 })Scales up only, no vertical movement.
HoverFX.attach(button, "Scale")
HoverFX.attach(button, "Scale", { Scale = 1.15 })Shrinks on hover instead of growing. Good for toggles or destructive actions.
HoverFX.attach(button, "Shrink")
HoverFX.attach(button, "Shrink", { Scale = 0.92 })Lifts upward only, no scale change.
HoverFX.attach(button, "Lift")
HoverFX.attach(button, "Lift", { Lift = 12 })Compresses and pushes down on mouse/touch hold. Best combined with LiftScale.
HoverFX.attach(button, "Press")
HoverFX.attach(button, "Press", { PressScale = 0.93, PressDepth = 5 })Animates a UIStroke border glow on hover.
HoverFX.attach(button, "Glow")
HoverFX.attach(button, "Glow", {
GlowThickness = 4,
GlowTransparency = 0.2,
GlowColor = Color3.fromRGB(120, 200, 255)
})Glow that continuously pulses in and out while the cursor is hovering.
HoverFX.attach(button, "PulseGlow")
HoverFX.attach(button, "PulseGlow", {
PulseGlowMinThickness = 1,
PulseGlowMaxThickness = 6,
PulseGlowSpeed = 0.5
})A fake drop-shadow sibling that grows on hover to simulate depth.
⚠️ Requires the button's parent to be a GuiObject (e.g. Frame, not ScreenGui directly).
HoverFX.attach(button, "ShadowDepth")
HoverFX.attach(button, "ShadowDepth", {
ShadowOffsetIn = 8,
ShadowGrowIn = 10,
ShadowOpacityIn = 0.45
})Reveals an animated underline bar sweeping from left to right on hover.
HoverFX.attach(button, "Underline")
HoverFX.attach(button, "Underline", {
UnderlineHeight = 3,
UnderlinePadding = 4
})Lightens the background, text and icon color on hover via lerp.
HoverFX.attach(button, "ColorShift")
HoverFX.attach(button, "ColorShift", {
ColorShiftAmount = 0.2,
AlsoTextColor = false -- skip text color shift
})Rotates and offsets an existing UIGradient on hover.
⚠️ Requires aUIGradientas a direct child of the element.
TextButton
├── UICorner
├── UIGradient ← required
└── LocalScript
HoverFX.attach(button, "GradientSlide")
HoverFX.attach(button, "GradientSlide", {
GradientRotation = 45,
GradientOffsetX = 0.35
})Sweeps a bright highlight bar diagonally across the element on hover.
HoverFX.attach(button, "ShineSweep")
HoverFX.attach(button, "ShineSweep", {
ShineDuration = 0.5,
ShineAngle = 25,
ShineTransparency = 0.65,
ShineWidthScale = 0.3,
ShineCooldown = 0.4
})Spawns a material-design expanding ripple at the exact click/touch point.
HoverFX.attach(button, "RippleClick")
HoverFX.attach(button, "RippleClick", {
RippleColor = Color3.fromRGB(255, 255, 255),
RippleTransparency = 0.5,
RippleScale = 2.5,
RippleDuration = 0.4,
RippleCooldown = 0.1
})Plays reusable hover and/or click sounds with built-in cooldown spam protection.
HoverFX.attach(button, "Sound", {
HoverSoundId = "rbxassetid://12345678",
ClickSoundId = "rbxassetid://87654321",
SoundVolume = 0.4,
SoundCooldown = 0.08
})Tilts and shifts the element in 3D to follow the cursor, creating a parallax effect.
HoverFX.attach(button, "ParallaxFollow")
HoverFX.attach(button, "ParallaxFollow", {
ParallaxMaxAngle = 10,
ParallaxMaxOffset = 8
})Subtly pulls the element toward the cursor when it enters a proximity radius.
HoverFX.attach(button, "MagneticHover")
HoverFX.attach(button, "MagneticHover", {
MagneticRadius = 90,
MagneticMaxOffset = 10,
MagneticStrength = 0.85
})Wiggles the first detected icon child (ImageLabel/ImageButton) on hover and click.
⚠️ Requires a childImageLabelorImageButton, ideally named"Icon".
HoverFX.attach(button, "IconWiggle")
HoverFX.attach(button, "IconWiggle", {
IconWiggleAngle = 14,
IconWiggleOffset = 4,
IconWiggleTime = 0.1
})Brief horizontal stretch-and-snap animation on hover.
HoverFX.attach(button, "Wave")
HoverFX.attach(button, "Wave", {
WaveAmount = 8,
WaveDuration = 0.2
})Touch-only hover fallback. Use on non-button Frames that need touch feedback.
HoverFX.attach(frame, "Mobile")
HoverFX.attach(frame, "Mobile", { Scale = 1.05, Lift = 4 })Auto-detects what's on the element (UIGradient, UIStroke, icons, text) and intelligently applies the best combination of effects.
HoverFX.attach(button, "Smart")
HoverFX.attach(button, "Smart", {
SmartIncludeShine = false,
SmartIncludeRipple = false,
SmartIncludeParallax = false,
PerformanceMode = true -- skip expensive effects
})All options are optional. Any unset value falls back to a sensible default.
| Option | Type | Default | Description |
|---|---|---|---|
Scale |
number |
1.08 |
Target scale on hover |
Lift |
number |
6 |
Pixel lift upward on hover |
TimeIn |
number |
0.12 |
Tween duration on hover enter (seconds) |
TimeOut |
number |
0.12 |
Tween duration on hover leave (seconds) |
EasingStyle |
Enum.EasingStyle |
Quad |
Easing style for tweens |
EasingDirection |
Enum.EasingDirection |
Out |
Easing direction for tweens |
UseUIScale |
boolean |
true |
Use UIScale for scaling (recommended) |
PressScale |
number |
0.96 |
Scale during press-down |
PressDepth |
number |
2 |
Pixel offset downward during press |
GlowThickness |
number |
3 |
UIStroke thickness on hover |
GlowTransparency |
number |
0.35 |
UIStroke transparency on hover |
GlowColor |
Color3 |
nil |
Override glow stroke color |
GradientRotation |
number |
35 |
Degrees added to gradient rotation |
GradientOffsetX |
number |
0.25 |
X offset added to gradient on hover |
ShineDuration |
number |
0.45 |
Duration of the shine sweep |
ShineAngle |
number |
20 |
Tilt angle of the shine bar |
ShineCooldown |
number |
0.25 |
Minimum seconds between sweeps |
RippleDuration |
number |
0.45 |
Duration of the ripple expand |
RippleColor |
Color3 |
White |
Ripple tint color |
RippleScale |
number |
2.25 |
Final ripple size multiplier |
RippleCooldown |
number |
0.12 |
Minimum seconds between ripples |
ShadowEnabled |
boolean |
true |
Enable shadow in ShadowDepth |
ShadowOffsetIn |
number |
6 |
Shadow Y offset on hover |
ShadowGrowIn |
number |
6 |
Shadow size growth on hover |
ShadowOpacityIn |
number |
0.65 |
Shadow transparency on hover |
ParallaxMaxAngle |
number |
6 |
Max tilt angle in degrees |
ParallaxMaxOffset |
number |
4 |
Max positional offset in pixels |
MagneticRadius |
number |
60 |
Pixel radius for magnetic attraction |
MagneticMaxOffset |
number |
5 |
Max pixel pull distance |
MagneticStrength |
number |
0.7 |
Pull strength multiplier (0–1) |
IconWiggleAngle |
number |
8 |
Wiggle rotation in degrees |
IconWiggleOffset |
number |
2 |
Wiggle lift in pixels |
PulseGlowMinThickness |
number |
2 |
Minimum pulse stroke thickness |
PulseGlowMaxThickness |
number |
4 |
Maximum pulse stroke thickness |
PulseGlowSpeed |
number |
0.6 |
Seconds per pulse half-cycle |
WaveAmount |
number |
4 |
Pixel width added during wave |
WaveDuration |
number |
0.25 |
Total wave animation duration |
HoverSoundId |
string |
nil |
Asset ID for hover sound |
ClickSoundId |
string |
nil |
Asset ID for click sound |
SoundVolume |
number |
0.35 |
Sound playback volume |
SoundCooldown |
number |
0.08 |
Minimum seconds between sounds |
ColorShiftAmount |
number |
0.12 |
Lerp amount toward white (0–1) |
AlsoTextColor |
boolean |
true |
Also shift text color |
Spring |
boolean |
false |
Use spring physics instead of tweens |
SpringSpeed |
number |
32 |
Spring responsiveness |
SpringDamping |
number |
0.82 |
Spring damping (less = more bounce) |
IgnoreDisabled |
boolean |
false |
Ignore Active = false state |
PerformanceMode |
boolean |
false |
Skip expensive effects (parallax, shine, ripple, wave, magnetic, icon wiggle) |
Any preset can use physics-based spring smoothing instead of tweens by adding Spring = true.
HoverFX.attach(button, "LiftScale", {
Spring = true,
SpringSpeed = 35, -- higher = snappier
SpringDamping = 0.75 -- lower = more bounce
})
-- Also works great with ParallaxFollow
HoverFX.attach(button, "ParallaxFollow", {
Spring = true,
SpringSpeed = 28
})Styles are named bundles of presets. Define once, apply everywhere.
| Style | Included presets |
|---|---|
Premium |
ShadowDepth, Press, LiftScale, Glow, PulseGlow, ColorShift, GradientSlide, RippleClick, Sound, ParallaxFollow, IconWiggle, MagneticHover, Wave |
Lite |
Press, LiftScale, ColorShift, Glow |
SmartPremium |
Smart |
-- Apply a built-in style
HoverFX.applyStyle(button, "Premium")
HoverFX.applyStyle(button, "Lite")
HoverFX.applyStyle(button, "SmartPremium")HoverFX.defineStyle("MyStyle", {
"Press",
"LiftScale",
"RippleClick",
"Glow"
}, {
Scale = 1.12,
GlowColor = Color3.fromRGB(100, 220, 255),
RippleColor = Color3.fromRGB(100, 220, 255),
Spring = true
})
-- Use it anywhere
HoverFX.applyStyle(button, "MyStyle")
-- Override options per-call
HoverFX.applyStyle(button, "MyStyle", { Scale = 1.05 })Multiple presets can be stacked on the same element. They run independently and all detach together.
HoverFX.attach(button, "LiftScale")
HoverFX.attach(button, "RippleClick")
HoverFX.attach(button, "Glow", { GlowColor = Color3.fromRGB(255, 180, 0) })
HoverFX.attach(button, "ShineSweep")
HoverFX.attach(button, "Sound", { HoverSoundId = "rbxassetid://123" })Attaches a single preset to a GuiObject.
HoverFX.attach(button, "LiftScale", { Scale = 1.1 })Removes all effects from a GuiObject and restores its original state.
HoverFX.detach(button)
HoverFX.detach(button, true) -- also destroys created instances (glow stroke, shadow, etc.)Attaches the full premium pack (all effects) in one call.
HoverFX.quick(button)
HoverFX.quick(button, { PerformanceMode = true }) -- lighter versionDefines a reusable named bundle of presets.
HoverFX.defineStyle("Neon", { "Glow", "PulseGlow", "RippleClick" }, {
GlowColor = Color3.fromRGB(0, 255, 180)
})Applies a defined style to a GuiObject.
HoverFX.applyStyle(button, "Neon")Applies a preset or style to every GuiButton found inside a container.
HoverFX.applyTo(screenGui, "LiftScale")
HoverFX.applyTo(screenGui, "Premium", nil, true) -- isStyle = trueTemporarily disables effects at runtime without detaching them.
local Attribute = require(HoverFX.Attribute)
Attribute.setDisabled(button, true) -- pause effects
Attribute.setDisabled(button, false) -- resume effects
Attribute.toggleDisabled(button) -- flip statelocal HoverFX = require(game.ReplicatedStorage.HoverFX)
local card = script.Parent
HoverFX.quick(card, {
Scale = 1.04,
Lift = 6,
Spring = true,
GlowColor = Color3.fromRGB(255, 255, 255),
RippleColor = Color3.fromRGB(255, 255, 255),
ShadowEnabled = true
})HoverFX.defineStyle("Neon", {
"Press", "LiftScale", "Glow", "PulseGlow", "RippleClick", "ShineSweep"
}, {
Scale = 1.08,
GlowColor = Color3.fromRGB(0, 255, 160),
GlowThickness = 4,
RippleColor = Color3.fromRGB(0, 255, 160),
PulseGlowSpeed = 0.4,
Spring = true
})
HoverFX.applyStyle(button, "Neon")local HoverFX = require(game.ReplicatedStorage.HoverFX)
HoverFX.applyTo(script.Parent.MenuFrame, "Lite")local Attribute = require(game.ReplicatedStorage.HoverFX.Attribute)
-- Pause all effects on a button
Attribute.setDisabled(button, true)
-- Resume after cutscene
cutsceneEnded:Connect(function()
Attribute.setDisabled(button, false)
end)local isMobile = game:GetService("UserInputService").TouchEnabled
HoverFX.quick(button, {
PerformanceMode = isMobile,
Spring = not isMobile
})| Preset | Requirement |
|---|---|
GradientSlide |
UIGradient must be a direct child of the element |
ShadowDepth |
Element's parent must be a GuiObject (not ScreenGui) |
IconWiggle |
Element must have a child ImageLabel or ImageButton (named "Icon" preferred) |
Glow / PulseGlow |
Works best when no existing UIStroke conflicts are present |
Smart |
Auto-detects all of the above — no setup needed |
Made with ❤️ for the Roblox dev community.