Fed up with MouseEnter and MouseLeave not working? Here's a module for you!

For a long time now, I’ve found MouseEnter and MouseLeave events to be rather unreliable. I’ve created a simple module to replace these for my new game, and thought I’d share it with you all in the hopes it would save some frustration and annoyance.

Does not support touch or game pads. Please report any bugs you find!

Note: Currently, once connected the events will continue to be fired until the GuiObject is destroyed, regardless of the UIs parent or visibility.

Note 2: Does not work for rotated elements.

Quick Start Guide:

  1. Copy the code at the bottom into a module and call it ‘MouseOverModule’, or a name of your choosing. Place it wherever you normally keep your modules.
  2. Use like this:
local MouseOverModule = require(game.ReplicatedStorage.Whatever.MouseOverModule)

local MouseEnter,MouseLeave = MouseOverModule.MouseEnterLeaveEvent(GuiObject)
   print("Mouse enter")
   print("Mouse leave")
  1. Delete the GuiObject when your done with it.
repeat wait() until game.Players.LocalPlayer
local Player = game.Players.LocalPlayer
local currentItems = {}

local module = {}
	function module.MouseEnterLeaveEvent(UIObj)
		if currentItems[UIObj] then
			return currentItems[UIObj].EnteredEvent.Event,currentItems[UIObj].LeaveEvent.Event
		local newObj = {}
		newObj.UIObj = UIObj
		local EnterEvent = Instance.new("BindableEvent")
		local LeaveEvent = Instance.new("BindableEvent")
		newObj.EnteredEvent = EnterEvent
		newObj.MouseIn = false
		newObj.LeaveEvent = LeaveEvent
		currentItems[UIObj] = newObj
		    if UIObj.Parent == nil then
		        -- item was removed
				currentItems[UIObj] = nil
		spawn(function() --Super hacky, but if you click a button then dont move your mouse, mouse over wont fire even if your mouse is over!
			local Mouse = Player:GetMouse()
			local X = Mouse.X
			local Y = Mouse.Y
			if X>newObj.UIObj.AbsolutePosition.X and Y>newObj.UIObj.AbsolutePosition.Y then
				if X<newObj.UIObj.AbsolutePosition.X+newObj.UIObj.AbsoluteSize.X and Y<newObj.UIObj.AbsolutePosition.Y+newObj.UIObj.AbsoluteSize.Y then
					if newObj.MouseIn ~= true then
						newObj.MouseIn = true
						if newObj.EnteredEvent then
	   return EnterEvent.Event,LeaveEvent.Event
	local Mouse = Player:GetMouse()
	local function IsInFrame(v)
		local X = Mouse.X
		local Y = Mouse.Y
		if X>v.AbsolutePosition.X and Y>v.AbsolutePosition.Y and X<v.AbsolutePosition.X+v.AbsoluteSize.X and Y<v.AbsolutePosition.Y+v.AbsoluteSize.Y then
			return true
			return false
		for i,v in pairs(currentItems) do
			if not IsInFrame(v.UIObj) then
				if v.MouseIn then
					v.MouseIn = false
		for i,v in pairs(currentItems) do
			if IsInFrame(v.UIObj) then
				if not v.MouseIn then
					v.MouseIn = true
return module

Note 4: There is a rather ugly spawn() function in the middle of the code. This was added as a quick way to ensure MouseEnter events will fire if the module is connect to an object the mouse is already inside. It’s not great practice so you can remove it if you like.


Thank you for making this. I needed this

1 Like

GuiObject.InputBegan(inputObject) and GuiObject.InputEnded(inputObject) where inputObj.UserInputType == UserInputType.MouseMovement have also been reliable ways to work around this in my experience.


This doesn’t work for me BillboardGui TextButton MouseMovement

I found if I used Button.MouseEnter then used a step bind and some quik maf to determine if it was in the button still, that was a simpler and equally efficient way of dealing with it.

Still, good job open sourcing.

1 Like

Maybe you could account for the mouse to be on only one object at an instant.

Great work, will definitely prove to be useful. I also heard from a friend that works at Roblox that MouseEnter and MouseLeave events are being improved to fix their issues. Can’t confirm if fixes will be released soon though.

Note 4: Doesn’t work for SurfaceGui’s neither it does account for parameters as ZIndex, Active or DisplayOrder. I might work those stuff in order to make them more reliable and keep the functionality of the default events.

Nice job, however.

I’ll probably get to adding the none-surface gui features at some point if you don’t beat me to it :slightly_smiling_face: (SurfaceGui mouse over would probably need a separate function and a lot more work)

1 Like

To clarify a bit more, if you’re referring to note 3, the mouse can move from one object to another in a single frame, and currently the code has no guarantee that the leave event will fire before the other objects enter event .

1 Like

This is really needed, too lazi to implement it now tho :stuck_out_tongue:

However I have some suggestions:

  1. Why not just return the MouseEnterLeaveEvent function?
    return MouseEnterLeaveEvent

  2. The function should return the Event signal instead of the Event object, so can connect directly:
    return EnterEvent.Event.Event, LeaveEvent.Event.Event

This would allow to do:

local MouseEnter, MouseLeave = MouseOverModule(GuiObject)
   print("Mouse enter")
   print("Mouse leave")

Which is slightly cleaner.

Alternativly allow include mouseenter/leave functions as parameters instead.
MouseOverModule(GuiObject, enterFunc, leaveFunc)

you are my everything.

This is so perfect

Updated with a new version:
-Fixed a bug causing the module to break if you call .MouseEnterLeaveEvent(GuiObject) on the same object more than once
-Within the same frame, mouse leave events now always fire before mouse enter events. Should help make the module overall easier to use.


This is a really great module and so perfect!
Btw I found a problem in your demonstration

It should be MouseEnter:connect(function() and MouseLeave:connect(function() instead.

I did. I finally did get fed up. Thank you.

In case anybody else wants to use this with roblox-ts, here’s a d.ts file for ya:

declare namespace MouseOver
function MouseEnterLeaveEvent( guiObject: GuiObject ) : [ RBXScriptSignal, RBXScriptSignal ]

export = MouseOver


Mouse Leave And Enter weren’t working, two seconds I installed this, it fixed all my problems. :smile: Thanks for this!