Templates & Frontend
Styling and Extending the Joomla Menu Module
Navigation is the part of a template people fight with longest, usually because they are styling markup they have not read. Here is what the menu module actually emits, which classes are load-bearing, and where to intervene when the…
What you get
A menu module renders a nested list. The outer element carries three classes, and each item carries several:
<ul id="mod-menu101" class="mod-menu mod-list nav">
<li class="nav-item item-101 default current active">
<a href="/" aria-current="page">Home</a>
</li>
<li class="nav-item item-102 deeper parent">
<a href="/catalogue">Catalogue</a>
<ul class="mod-menu__sub list-unstyled small">
<li class="nav-item item-110"><a href="/catalogue/hats">Hats</a></li>
</ul>
</li>
</ul>
The classes worth knowing:
item-{id}— the menu item's ID. The hook for styling one specific entry without adding markup.default— the site's home item.current— the item matching the page being viewed.active— the item and its ancestors on the current branch. This is what highlights a parent while a child is open.parentanddeeper— an item that has children, and one that opens a further level.
current versus active is the distinction people miss, and it is the reason a top-level entry either does or does not stay highlighted on subpages.
Style against those, not against structure
.mod-menu {
display: flex;
gap: 0;
list-style: none;
margin: 0;
padding: 0;
}
.mod-menu .nav-item > a {
display: block;
padding: 0.85rem 1.25rem;
text-decoration: none;
}
.mod-menu .current > a {
box-shadow: inset 0 -2px 0 currentColor;
}
Selectors built on position — li:nth-child(3) — break the first time someone reorders the menu in the administrator, which they will, without telling you.
Overriding the layout
When the markup itself needs to change, copy the layout into your template:
mkdir -p templates/yourtemplate/html/mod_menu
cp modules/mod_menu/tmpl/default.php templates/yourtemplate/html/mod_menu/
Give it a distinct name — mainnav.php rather than default.php — and select it in the module's Advanced tab. That way one override does not silently change every menu on the site, including the one in the footer.
Inside, keep the current/active logic intact. It is a few lines, it is easy to drop while restructuring the markup, and losing it means no menu on the site ever highlights anything again.
Dropdowns without breaking keyboard access
A hover-only dropdown is unreachable by keyboard and unusable on touch. At minimum, open on focus as well:
.mod-menu .parent > .mod-menu__sub {
display: none;
}
.mod-menu .parent:hover > .mod-menu__sub,
.mod-menu .parent:focus-within > .mod-menu__sub {
display: block;
}
:focus-within costs one line and makes the submenu reachable by tabbing. For anything more elaborate — click to open, escape to close, arrow keys between items — use a real button as the trigger and manage aria-expanded, rather than hanging behaviour off a link that also navigates somewhere.
Depth is a setting, not a template problem
Before overriding anything, check the module's own options: Start Level, End Level and Always show sub-menu items cover most "I only want the second level here" requirements. A separate module instance with different level settings is usually cleaner than one layout full of conditions.
One list, one purpose
If the top-level menu needs more than about five entries, the problem is the menu structure, not the CSS. Nesting thirteen items into dropdowns hides the mess rather than fixing it — we went through that in a separate piece on menu length.