I wish it was as simple as changing some CSS, unfortunately it's not...
In _navigation.less, Magento sets the top
property of the submenu elements as 0 !important
. It does this within the 'lib-main-navigation-desktop' mixin.
To change this, your theme has to overwrite _navigation.less. You do this by copying vendor/magento/theme-frontend-blank/web/css/source/_navigation.less into your custom theme under {theme-directory}/web/css/source/_navigation.less.
Next, open lib/web/css/source/lib/_navigation.less and find .lib-main-navigation-desktop
(line 219-443). Copy this entire block of code to the top of the _navigation.less file in your theme. Rename it to something like .my-main-navigation-desktop
.
Find this code in the mixin:
.submenu {
top: 0 !important;
left: 80% !importatn;
}
Delete the line top: 0 !important;
. Now find the line .lib-main-navigation-desktop();
near the bottom of the file and replace it with .my-main-navigation-desktop();
, or whatever you named the mixin.
Now that we have removed the hard-coded top property, if you recompiled the css at this point you'd find that the submenus are now vertically positioned beneath the parent, which is not what we want. To get them to be at the correct level, we need to make some changes to the javascript that controls the menu.
To do this, we create a new module with typical boilerplate files.
Copy lib/web/mage/menu.js into {your_module_dir}/view/frontend/web/js/menu.js.
In this file, find this section of code (lines 335-344):
"mouseenter .ui-menu-item": function (event) {
var target = $(event.currentTarget),
ulElement,
ulElementWidth,
width,
targetPageX,
rightBound;
if (target.has('ul')) {
ulElement = target.find('ul');
Right after that last line, insert this:
if (ulElement[0] && !ulElement[0].classList.contains('level0')) {
var cssTop = event.target.offsetTop,
ele = $(ulElement[0]);
window.setTimeout(function () {
ele.css('top', cssTop).animate({top: cssTop}, 20);
}, this.options.delay-10);
}
The reason we are using the timeout and animate is that there is a script somewhere along the line that is setting the css top property (I cannot find where this occurs) and so we have to 'force' it to use the correct value.
In our module, we need to tell magento to use this script so we create a requirejs-config.js file here: {module_dir}/view/frontend/web/requirejs-config.js
with this contents:
var config = {
map: {
"*": {
menu: "Your_Module/js/menu"
}
}
};
Now we just need to run some shell commands from the magento root:
rm -rf pub/static/*
rm -rf var/view_preprocessed/*
php bin/magento setup:upgrade
php bin/magento cache:flush
Ok, that should do it!