Create a minimal hover menu on Shopify with just CSS

Ian Marshall
7 min readFeb 17, 2023

Shopify gives you a lot of control over how your navigation appears without needing to know any complex code. Their click and drag functionality lets you update navigation throughout your site as well as adding sub-menus. There are many ways to style these elements on the front end, but my favorite performant hover menu can simply use CSS without any Javascript. These instructions will help you create nested menus that reveal sub-menus when you hover over their parent elements and disappear when you navigate elsewhere.

First, in your Shopify admin, set up menu items in Online Store > Navigation. Here you can either update an existing menu or create a new one — my example will use Main Menu.

The first level of elements here will become the first options in the sub-menu: Shop, Media, and Story. Each of these elements has their own destination URL as well as its own sub-menu items. You can add items, click and drag them to elevate them to the first sub-menu or put them further down into another submenu. The margins on the left indicate that the item is an element within the parent menu.

Next, we’ll use HTML and Liquid to iterate through this menu structure and populate the HTML element that you’ll see on the front end. Create a file in /sections called “custom-header.liquid” and include the following code:

<div class=”menu-banner”>
<h2>Menu</h2>
<div class=”sub-menu-main”>
<ul class=”sub-menu”>
{% for link in linklists[section.settings.main_menu].links %}
<li class=”{% if link.links != blank %}submenu2-hover {{ link.handle }} {% endif %} {% if link.active %}active{% endif %}” ><a href=”{{ link.url }}”>{{ link.title }}</a>
{% if link.links != blank %}
<ul class=”sub-menu2 sub-menu2-{{ link.handle }}”>
{% for childlink in link.links %}
<li class=”{% if link.active %}active{% endif %}”><a href=”{{ childlink.url }}”>{{ childlink.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</div>
</div>

The header should also include other items in your navigation, like the merchant’s logo, account navigation, and whatever else you’d like to include. For this example, I’ll just stick with the hover menu. You’ll also need a schema section in this file that will allow you to associate the Main Menu you made in the Shopify admin:

{% schema %}
{
"name": "Header",
"settings":
[
{
"type" : "image_picker",
"id" : "desktop_logo",
"label" : "Logo"
},
{
"type" : "link_list",
"id" : "main_menu",
"label" : "Main Menu"
},
{
"type" : "image_picker",
"id" : "menu_background",
"label" : "Menu Background"
}
]
}
{% endschema %}

This schema gives the merchant control over which menu they are associating with the navigation as well as the logo for the main menu and the background image. You can make these sections very customizable, but, again, I’ll keep it simple and focus on getting the functionality of the hover menu in place.

The general HTML structure you’ll need to set up this menu includes a header, then some nested elements that will be revealed on hover. The general structure here includes a parent <div> (“menu-banner”), a child <div> (“sub-menu-main”) and then an unordered list <ul> (“sub-menu”) that includes the first set of sub-menu items.

The general liquid structure iterates through the menu items with two nested for-loops and some if-statements that prevent undesired behavior. The first for-loop takes the link_list produced by the Main Menu we set up in Navigation and creates a list item for each item in the list. Within this for-loop, there are if-statements that add classes if the link is active or being hovered over, which we will build upon with CSS to reveal elements when they are hovered on and hide them again when you navigate away. It’s also worth noting that the handle of each menu item, like “media” or “shop” is added to the <li>’s class, which will be relevant later when styling pseudo-elements.

Once each of these sub-menu items has been rendered as a <li>, there is an additional if-statement that checks whether these links have links of their own. If the if-statement evaluates as true, another <ul> element is generated with its own for-loop to add in sub-menu items as their own <li>s. This is as far down as my menu goes, but additional for-loops and if-statements could be included to create even more sub-menus.

Now that the logic and architecture of the hover menu is in place, we need to create some CSS to style this and make it functional. Add this to your dedicated CSS file or between two <style> tags in your custom-header file:

.menu-banner {
position: relative; /* The position of the parent element is set to relative */
width: 10%;
}

.menu-banr h2 {
color: #E94917;
font-family: 'Area-RegularInktrap';
font-family: 'Area-BoldInktrapExtended';
}

.sub-menu-main {
position: absolute; /* the position of the first sub-menu is set to absolute */
top: 0px;
left: 80px;
border-left: 1px solid #e94917;
padding-left: 6px;
visibility: hidden; /* the position of the first sub-menu is set to hidden */
opacity: 0;
}

/*
The sub-main-meu has visibility set to hidden, but when the menu-banner
element is hovered over, the sub-main-menu is set to visible.
*/

.menu-banner:hover .sub-menu-main {
opacity: 1;
visibility: visible;
}

.sub-menu li a {
display: block; /* the links in the sub-menu have display set to block */
width: 200px; /* this width allows you to stay hovering 'on' the element
as you navigate to the right. Without this width, you would
hover off the element and the sub-menu would disappear.*/
}

/*
The ::before here creates a pseudo-element that is the first child of the
selected element. In this case we are using it to create the red bar that
extends from the sub-menu items to the left to connect the text “Menu” and
the child elements
*/
.sub-menu::before {
content: '';
position: absolute;
left: -31px;
top: 9px;
width: 26px;
height: 1px;
background-color: #e94917;
}

.sub-menu2 {
position: absolute; /* the links second sub-menu have position set to
absolute */
left: 97px;
top: 0px;
padding-left: 10px;
}

.sub-menu-main {
position: absolute;
top: 0px;
left: 80px;
border-left: 1px solid #e94917; /* this creates the vertical line to the
left of the submenu to show you are looking
at a new list of navigation items */
padding-left: 6px;
visibility: hidden;
opacity: 0;
}

/*
The ::after here creates a pseudo-element that is the last child of the
selected element. In this case we are using it to create the red bar that
extends from the menu item to the right
*/
.sub-menu2::after {
content: '';
position: absolute;
left: 3px;
top: 0px;
height: 100%;
background-color: #e94917;
width: 1px;
}

/*
This transition delay keeps the items visible for a bit after the mouse moves
off the menu items so you can navigate between them without them disappearing
*/
li.submenu2-hover:hover .sub-menu2, .sub-menu-2-shop:hover {
visibility: visible;
transition-delay: 250ms;
}

/*
Since the sub-menu items “Shop” and “Media” are different widths, we need
custom CSS for each to define the pseudo elements that will stretch out to
the right to their respective sub-menus
*/

ul.sub-menu2-shop::before {
content: '';
position: absolute;
top: 12px;
width: 31px;
height: 1px;
background-color: #e94917;
left: -32px;
}

ul.sub-menu2-media, ul.sub-menu2-shop{
padding-left: 10px;
}

ul.sub-menu2-media{
top: 24px;
}

ul.sub-menu2-media::before {
content: '';
position: absolute;
top: 8px;
width: 26px;
height: 1px;
background-color: #e94917;
left: -27px;
}


.sub-menu2-shop::after, .sub-menu2-media::after {
content: '';
position: absolute;
left: 3px;
top: 0px;
height: 100%;
background-color: #e94917;
width: 1px;
}

/* This styling makes the links red, then grey on hover */
.sub-menu2 li.active>a, .sub-menu li.active>a {
color: #e94917;
}
.sub-menu2 li.active>a:hover, .sub-menu li.active>a:hover {
color: #808080;
}

Much of the CSS that deals with top, bottom, left, and right as well as the padding is idiosyncratic to the menu you are working with — you want to make sure the lists of elements and the pseudo elements are all properly sized and spaced, so you will have to play around with these values to make sure that the menu is portioned the way you expect.

The final result is a sleek, minimal menu that can be easily customized to add new sub-menus. Best of all, since this doesn’t require any Javascript, your storefront will not be slowed down by any additional scripts.

--

--