cwspear/bootstrap-hover-dropdown
jQuery plugin that brings Bootstrap dropdowns to life on hover. Adds configurable delay, menu close on mouseout, and touch-friendly behavior while keeping Bootstrap’s markup and styles. Useful for navbars and multi-level menus.
Install via Composer
composer require cwspear/bootstrap-hover-dropdown
or via npm (if using the JS bundle):
npm install bootstrap-hover-dropdown
Include CSS/JS
import 'bootstrap-hover-dropdown';
<link href="{{ asset('vendor/bootstrap-hover-dropdown/css/bootstrap-hover-dropdown.css') }}" rel="stylesheet">
<script src="{{ asset('vendor/bootstrap-hover-dropdown/js/bootstrap-hover-dropdown.js') }}"></script>
Basic Usage Convert a standard Bootstrap dropdown to hover-triggered:
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Hover Me
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
</div>
</div>
No extra JS needed—the package auto-initializes on pages with Bootstrap 4/5.
Replace click-triggered dropdowns (e.g., user profile menus) with hover for faster navigation:
<div class="dropdown d-inline-block ms-3">
<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">
<i class="fas fa-user-circle"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/profile">Profile</a>
<a class="dropdown-item" href="/settings">Settings</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/logout">Logout</a>
</div>
</div>
data-hover-dropdown to opt-in/out per dropdown:
<div class="dropdown" data-hover-dropdown="true">...</div>
$(document).ready(function() {
$('.dropdown').hoverDropdown(); // jQuery syntax
// OR
new HoverDropdown(); // Vanilla JS (if using ES6 module)
});
<div class="dropdown {{ auth()->user()->is_admin ? 'data-hover-dropdown' : '' }}">
<!-- Dropdown content -->
</div>
@component('components.dropdown', [
'items' => ['Dashboard', 'Reports', 'Logout'],
'hover' => true
])@endcomponent
<div class="dropdown"
data-hover-dropdown
data-hover-dropdown-delay="300" <!-- 300ms delay -->
data-hover-dropdown-auto-close="false"> <!-- Persist on hover -->
</div>
$.fn.hoverDropdown.defaults = {
delay: 200,
autoClose: true
};
<div class="dropdown {{ request()->isMobile() ? 'data-hover-dropdown="false"' : '' }}">
Helper in app/Helpers.php:
if (!function_exists('isMobile')) {
function isMobile() {
return request()->userAgent() === 'mobile';
}
}
public function testHoverDropdownInitializes()
{
$html = '<div class="dropdown" data-hover-dropdown></div>';
$this->view->make('partials.dropdown', ['html' => $html]);
$this->assertContains('hoverDropdown', $this->view->renderSection('scripts'));
}
it('opens dropdown on hover', () => {
const dropdown = document.querySelector('.dropdown');
fireEvent.mouseEnter(dropdown);
expect(dropdown.querySelector('.dropdown-menu')).toBeVisible();
});
Bootstrap Version Conflicts
bootstrap@4.x or bootstrap@5.x is included after this package.
<!-- Correct order -->
<link href="css/bootstrap.css" rel="stylesheet">
<link href="css/bootstrap-hover-dropdown.css" rel="stylesheet">
CSS Specificity Wars
!important sparingly; target the package’s classes:
.dropdown-menu {
z-index: 1050 !important; /* Match Bootstrap’s default */
}
Double Initialization
hoverDropdown() twice on the same element breaks functionality.data-hover-dropdown or check for existing instances:
if (!$('.dropdown').data('hoverDropdown')) {
$('.dropdown').hoverDropdown();
}
Touch Devices
if ('ontouchstart' in window) {
$('.dropdown').removeData('hoverDropdown');
}
console.log($.fn.hoverDropdown); // Should log the function
$('.dropdown').on('mouseenter', () => console.log('Hovered!'));
$.fn.hoverDropdown.defaults.autoInit = false;
Custom Animations Override the default fade/slide with CSS transitions:
.dropdown-menu {
transition: opacity 0.2s, transform 0.2s;
opacity: 0;
transform: translateY(10px);
}
.dropdown-menu.show {
opacity: 1;
transform: translateY(0);
}
Server-Side Rendering (SSR) For Laravel Vapor/Edge, ensure JS runs after hydration:
document.addEventListener('DOMContentLoaded', () => {
new HoverDropdown();
});
Accessibility Add ARIA attributes dynamically:
$.fn.hoverDropdown = function() {
this.attr('aria-haspopup', 'true');
// ... rest of the plugin
};
Laravel Mix Optimization Extract the package’s JS into a separate chunk:
// webpack.mix.js
mix.js('resources/js/bootstrap-hover-dropdown.js', 'public/js')
.extract(['bootstrap-hover-dropdown']);
data-hover-dropdown="false" on dropdowns that shouldn’t hover (e.g., mobile-only menus).// resources/scss/custom.scss
@import '~bootstrap-hover-dropdown/scss/bootstrap-hover-dropdown';
$hover-dropdown-bg: #343a40; // Dark theme
$hover-dropdown-border-color: darken($hover-dropdown-bg, 5%);
document.addEventListener('livewire:init', () => {
new HoverDropdown();
});
if (localStorage.getItem('dark-mode')) {
$('.dropdown-menu').addClass('bg-dark text-light');
}
How can I help you explore Laravel packages today?