jQuery / Method / .hover() - Enter and Leave Events with on Alternatives
jQuery's .hover() is shorthand for attaching functions when the mouse pointer enters and leaves an element. It has been deprecated since jQuery 3.3; use .on("mouseenter", ...) and .on("mouseleave", ...) for new code. Deprecation differs from removal: both approaches below were executed with 3.7.1 and 4.0.0.
Two handlers or one shared handler
In the .hover() API, the two-function form calls the first on entry and the second on exit. It binds mouseenter and mouseleave, rather than registering an event named hover.
| Legacy form | Replacement | Meaning |
|---|---|---|
| .hover(enter, leave) | .on("mouseenter", enter).on("mouseleave", leave) | Separate entry and exit handlers |
| .hover(handler) | .on("mouseenter mouseleave", handler) | One function for both events |
mouseenter concerns the element boundary. Moving from inside a parent onto its child span does not count as entering the parent again. Unlike mouseover, which can propagate from child transitions, it avoids those unwanted repeated calls.
Complete comparison of hover and on
Save all the code as UTF-8 in hover-demo.html and open it in a browser. Internet is needed to load the official jQuery file 4.0.0 from its content delivery network (CDN); no package installation is required. Both examples, child transitions, dynamic elements, keyboard interaction and removal were executed with 3.7.1 and 4.0.0.
Alpha uses legacy .hover(); Beta uses .on() delegated from #cards. Card content always remains visible and only its highlight changes. Beta also receives focusin/focusout handling for keyboard focus.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hover events and keyboard focus</title>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<style>
body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 24px auto; padding: 16px; }
button { font: inherit; margin: 6px; }
.card { display: block; padding: 20px; border: 2px solid teal; background: white; }
.active { background: #d9f4ef; }
.card:focus-visible { outline: 3px solid #af5f00; outline-offset: 3px; }
</style>
</head>
<body>
<h1>Compare legacy hover and on</h1>
<button type="button" id="add">Add modern card</button>
<button type="button" id="bind">Bind modern</button>
<button type="button" id="unbind">Unbind modern</button>
<button type="button" id="reset">Reset</button>
<div id="cards">
<button type="button" class="card legacy">Legacy <span>Alpha</span></button>
<button type="button" class="card modern">Modern <span>Beta</span></button>
</div>
<p id="result" aria-live="polite"></p>
<script>
$(function () {
const $cards = $("#cards");
let legacyEntries = 0;
let modernEntries = 0;
function report() {
$("#result").text(`legacy enters=${legacyEntries} | modern enters=${modernEntries}`);
}
$(".legacy").hover(function () {
legacyEntries++;
$(this).addClass("active");
report();
}, function () { $(this).removeClass("active"); });
function bindModern() {
$cards.off(".hoverLesson").on(
"mouseenter.hoverLesson mouseleave.hoverLesson focusin.hoverLesson focusout.hoverLesson",
".modern", function (event) {
const $card = $(this);
if (event.type === "mouseenter") { $card.data("inside", true); modernEntries++; }
if (event.type === "mouseleave") $card.data("inside", false);
if (event.type === "focusin") $card.data("focused", true);
if (event.type === "focusout") $card.data("focused", false);
$card.toggleClass("active", Boolean($card.data("inside") || $card.data("focused")));
report();
}
);
}
$("#bind").on("click", bindModern);
$("#unbind").on("click", function () {
$cards.off(".hoverLesson");
$(".modern").removeClass("active").removeData("inside focused");
});
$("#add").on("click", function () {
if (!$("#gamma").length) $('<button type="button" id="gamma" class="card modern">Modern <span>Gamma</span></button>').appendTo($cards);
});
$("#reset").on("click", function () {
$("#gamma").remove();
$(".card").removeClass("active").removeData("inside focused");
legacyEntries = 0;
modernEntries = 0;
bindModern();
report();
});
bindModern();
report();
});
</script>
</body>
</html>
Both entry counters initially read zero:
legacy enters=0 | modern enters=0
- Move into Legacy Alpha. Its background changes and legacy enters increases once. Moving onto its inner Alpha span does not increase it again.
- Leave the card to remove its highlight. Modern Beta similarly increments modern enters on entry.
- Add modern card creates Gamma. The existing delegated parent handler processes it without binding another handler to the new button.
- Use Tab to focus Beta. Its background and focus outline appear. Moving the pointer away retains its background while focus remains; both pointer and focus must leave to clear it.
- Unbind modern disables only Modern handling. Alpha's legacy hover still works.
- Repeating Bind modern does not duplicate calls. Reset removes Gamma and highlights, zeros counters and reconnects Modern handling.
inside and focused track pointer and focus separately. Either keeps the highlight active. Clearing the class unconditionally on mouseleave would otherwise remove the visual state even while keyboard focus remains.
Dynamic elements and removal scope
The delegated .on() form attaches to a stable parent and supplies a descendant selector. Here #cards remains in place and only .modern buttons match. Legacy .hover() binds directly to the selection present at registration; do not expect future elements to inherit it automatically.
hoverLesson is an event namespace. bindModern() first calls .off(".hoverLesson") to remove only its own handlers, then binds again. Bare .off() or broad .off("mouseenter mouseleave") could remove another component's events too; keep the scope specific.
In the ordinary function handler, this is the Modern card being handled. It differs from event.target, which may be the inner span where the event originated. Do not replace it blindly with an arrow function if you need that this binding.
Check the single function form and input choices
Run this complete fragment in the developer tools Console to observe one function receiving both event types. It triggers events on a temporary button without inserting it and removes it afterward. Real pointer movement is demonstrated in the full example.
const events = [];
const $button = $("<button>Temporary</button>");
$button.hover(function (event) { events.push(event.type); });
$button.trigger("mouseenter").trigger("mouseleave");
console.log(events.join(", "));
$button.remove();
mouseenter, mouseleave
This means entry and exit are handled, not every moment of mouse movement. For a shared handler, checking event.type and setting a clear state can avoid a simple toggle becoming inverted after other state changes.
Do not rely on mouse hover alone to reveal essential content on touch screens. Make important features accessible by click/tap and keyboard too. For decoration alone, consider whether CSS :hover and :focus-visible suffice. This example always displays card content and changes only highlighting.




