I'm trying to build a notification dropdown that appears when I hover over the bell icon. I’ve styled the bell and the dropdown with flashy neon effects, and I even added a small invisible .notification-hover-bridge element between them to prevent the dropdown from disappearing when the mouse moves from the bell to the dropdown.
But here’s the problem:
The dropdown still closes too early, because there's a small gap between the bell and the dropdown. When I move my mouse from the bell to the dropdown, even if I go quickly and carefully, it triggers a mouseleave event before I can reach the dropdown — so it vanishes.
What I want is a seamless hover area that includes:
the bell icon,
the space between,
and the dropdown itself.
So I need a fix where the mouse can travel across that invisible bridge without the dropdown disappearing — basically, one unified hover zone that keeps the dropdown visible as long as I’m somewhere in that area.
I am using angular/scss:
<div class="notification-wrapper"
(mouseenter)="showDropdown = true"
(mouseleave)="showDropdown = false">
<!-- Notification Bell -->
<div class="notification-bell" [attr.data-count]="getUnreadCount()">
🔔 {{ getUnreadCount() }}
</div>
<!-- Hover Bridge (optional, but safer with nested timing issues) -->
<div class="notification-hover-bridge"></div>
<!-- Dropdown -->
<ul class="notifications-dropdown" *ngIf="showDropdown">
<li class="mark-all-container" *ngIf="notifications.length > 0">
<span>Notifications</span>
<button (click)="markAllAsRead()">Mark All as Read</button>
</li>
<li *ngFor="let n of notifications">
<a *ngIf="n.link" [routerLink]="n.link" (click)="markAsRead(n.id)">
{{ n.message }}
</a>
<span *ngIf="!n.link">{{ n.message }}</span>
<button (click)="markAsRead(n.id)">Mark as Read</button>
</li>
<li *ngIf="notifications.length === 0">
No notifications yet!
</li>
</ul>
</div>
<button *ngIf="loggedIn" (click)="goToMyShit()" class="btn btn-warning">💩 My Shit</button>
</div>
and
.notification-wrapper {
position: relative;
display: inline-block;
z-index: 1000; // ensures it's above other elements if needed
}
.notification-bell {
position: relative;
display: inline-block;
font-size: 1.5rem;
color: #ff0;
cursor: pointer;
text-shadow: 0 0 10px #ff0, 0 0 20px #ff0;
transition: transform 0.2s ease-in-out;
&:hover {
transform: scale(1.1);
}
&::after {
content: attr(data-count);
position: absolute;
top: -8px;
right: -12px;
background-color: #f00;
color: white;
font-size: 0.7rem;
font-weight: bold;
padding: 4px 6px;
border-radius: 50%;
box-shadow: 0 0 10px #f00, 0 0 20px #f00;
z-index: 10;
}
.notification-hover-bridge {
position: absolute;
pointer-events: auto;
top: 100%;
left: 0;
width: 100%;
height: 12px; // tweak as needed
z-index: 999;
}
.notifications-dropdown {
display: none;
position: absolute;
top: calc(100% + 2px);
left: 50%;
transform: translate(-50%);
width: 300px;
max-height: 400px;
overflow-y: auto;
background: #1a1a1a;
border: 2px solid #ff00ff;
border-radius: 10px;
padding: 0px;
margin-top: 0px;
list-style: none;
z-index: 1001;
box-shadow: 0 0 20px #ff00ff, 0 0 30px #ff00ff;
.mark-all-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: #2a2a2a;
border-bottom: 2px solid #ff00ff;
color: #ff0;
font-weight: bold;
font-size: 1rem;
text-shadow: 0 0 10px #ff0;
button {
background: none;
border: 1px solid #ff0;
color: #ff0;
padding: 4px 10px;
font-size: 0.75rem;
border-radius: 5px;
text-shadow: 0 0 5px #ff0;
box-shadow: 0 0 10px #ff0;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background-color: #ff0;
color: #121212;
box-shadow: 0 0 20px #ff0, 0 0 40px #ff0;
}
}
}
li {
padding: 10px 16px;
border-bottom: 1px solid #333;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.95rem;
color: #fff;
text-shadow: 0 0 5px #0f0;
transition: background-color 0.3s;
&:hover {
background-color: #2a2a2a;
}
button {
background: none;
border: 1px solid #0ff;
padding: 4px 8px;
color: #0ff;
border-radius: 6px;
font-size: 0.75rem;
cursor: pointer;
text-shadow: 0 0 5px #0ff;
box-shadow: 0 0 10px #0ff;
transition: all 0.3s ease;
&:hover {
background-color: #0ff;
color: #121212;
box-shadow: 0 0 20px #0ff, 0 0 40px #0ff;
}
}
}
li:last-child {
border-bottom: none;
}
}
&:hover .notifications-dropdown {
display: block;
}
}
For some reason, the dropdown keeps disappearing before the mouse can get to it (from the bell). I have tried adding the invisible div, a wrapper, and playing with the positions. Nothing works!!!
So basically you want to allow a grace period of 100-ish milliseconds before setting showDropdown = false
. That should cover the distance between the bell and the menu.
var globalTimeoutId = null;
var bell = document.querySelector(".bell")
var menu = document.querySelector(".menu")
function setElementAsMenuActivator(elem) {
elem.addEventListener('mouseenter', function() {
clearInterval(globalTimeoutId)
showMenu()
})
elem.addEventListener('mouseleave', function() {
globalTimeoutId = setTimeout(function() {
hideMenu()
}, 200)
})
}
setElementAsMenuActivator(menu)
setElementAsMenuActivator(bell)
function showMenu() {
menu.classList.add("active")
}
function hideMenu() {
menu.classList.remove("active")
}
div {
border: 1px solid red;
padding: 20px;
}
.menu {
display: none;
}
.menu.active {
display: block;
}
<div class="bell">hover me</div>
<hr>
<div class="menu">menu</div>