Keeping dropdown navigation menus CSS-only is a tricky job. It’s true that such menus are not touch-friendly in nature, but still, they are handy when you want them without JavaScript.

In this article, I’m going to share how I keep my dropdown menus CSS-only, which I also may extend further to bring about a touch-powered menu using CSS-hacks or JavaScript.

Before going further, I’d recommend you to go through Simple CSS Navigation Menu to see the basic boilerplate of what we are going to use here.

Demo the CSS-only Dropdown Menu

The Idea

As I told above, this one is extended from our simple CSS horiztonal menu. The menu is adjusted to include the sub-menus which also appear fine on smaller screen sizes. We’ll be playing with CSS position property to include and place the sub-menus.

Sub-menu logic

First off, we’ll be setting up the base for our menu. Below go the settings for the navigation wrapper (.nav), the menu link-list (.menu), and the children. Note that the CSS below is based on the mobile-first approach of responsive web design.

Our sub-menus should break out of the flow on bigger screens, which is well taken care by the position CSS property. If we set the position of parent menu-item to relative, then it will become easier for us to make sub-menus behave accordingly in breaking the flow.

SCSS

Study the following SCSS, and see how we aligned the nav-wrapper, menu, and menu-items for the devices with smaller screens.

Basic Alignment

The display menu-items and hyperlinks can be set to block-level to make them appear like solid menu-item with a size. The size can be maintained by providing some padding to the hyperlinks, and the separations between the items can be made using the border.

Fade effect

We all know setting display to none normally, and making it block-level again on hover, active, and focus states will do the job; but this way we won’t be able to get that slick fade-in, fade-out effect.

For the fading effect, we’ll be hiding the sub-menu by setting the visibility to hidden, and opacity and height to 0. Next we can add a transition handler to the .sub-menu for these three properties, and will be adjusting them again on hover, active, and focus states.

Variable Definitions

The below mentioned variable definitions help you modify later on the look, feel, and sizing of he menu and it’s elements:

//
// Variable definitions
//
// 1. Background-color for the top-level menu
// 2. Color for the top-level menu
// 3. Border-color settings for the top-level menu and items
// 4. Background-color for the sub-menus
// 5. Color for the sub-menus
// 6. Border-color settings for the sub-menus and items
// 7. Spacing/sizing for the items across the menu
//

// 1 //
$menu-bg: black;
$menu-bg-hover: #07c;

// 2 //
$menu-co: #ddd;
$menu-co-hover: #fff;

// 3 //
$menu-bo: rgba(white, .1);

// 4 //
$sub-menu-bg: #eee;
$sub-menu-bg-hover: white;

// 5 //
$sub-menu-co: #222;
$sub-menu-co-hover: black;

// 6 //
$sub-menu-bo: rgba(black, .1);

// 7 //
$menu-link-padding: 1em;

Initializing the menu

In the next section, we are going to start it off with some initial settings like zeroing down the margins and setting up with borders for the required elements.

//
// The Navigation Menu
//
// Below defined are all the ins and outs for our navigation menu, 
// referred to as `.nav`. You may wrap the below settings inside 
// `.nav` or any other suitable class too, to make it more strict 
// in terms of location.
//
// Example:
//
// <nav class="nav">
//   <ul class="menu">
//     <li class="menu-item">
//       <a href="/path/to/.../">...</a>
//     </li>
//     <li class="menu-item">
//       <a href="/path/to/.../">...</a>
//     </li>
//     <li class="menu-item has-children">
//       <a href="/path/to/.../">...
//         <span class="dropdown-icon"></span>
//       </a>
//       <ul class="sub-menu">
//         <li class="menu-item">
//           <a href="/path/to/.../">...</a>
//         </li>
//         <li class="menu-item">
//           <a href="/path/to/.../">...</a>
//         </li>
//       </ul>
//     </li>
//     <li class="menu-item">
//       <a href="/path/to/.../">...</a>
//     </li>
//   </ul>
// </nav>
//

// Zero-down the margin and the padding for menu-lists if not done
// in the base reset
.menu,
.sub-menu {
  margin: 0;
  padding: 0;
}

// Quick border settings
.menu-item,
.sub-menu,
.dropdown-icon {
  border: 0 solid;
}

[wp_ad_camp_1]

Main styling for the menu

Here comes the major styling part, where we will be crafting the menu component and it’s sub-components, with the animation part, the dropdown icons, and some other supporting sections. Note that the below code is written considering the mobile-first approace of respoinsive web design.

// 
// The Menu-list
//
// Below goes the set-up for our menu-list, carrying several menu-items
// (`.menu-items) as well as sub-menus (`.sub-menu`).
//
// 1. Make the menu-item take all available space
// 2. Make the menu-items handle the fly-outs smoothly by not allowing 
//    break the flow
// 3. Add a separation border except for the last menu-item
// 4. Size-up, align, and make the hyperlinks acquire all the 
//    available space
// 5. Add a seperating border to the sub-menu
// 6. Make the sub-menu hidden
// 7. Give a transition (fade-in/fade-out effect) to the fly-outs
// 8. Hide the menu-items inside a hidden sub-menu to avoid 
//    border-flickering
// 9. Make the sub-menu visible on hovering, selecting, or focusing the
//    parent menu-item
//10. Make the menu-items inside a hidden sub-menu visible and 
//    available when its parent menu-item is hovered, selected, or
//    focused.
//11. Add some rotation to the dropdown icon when the parent menu item 
//    gets hovered, selected, or focused
//12. Make the dropdown icon size-up equally as the containing 
//    menu-item, and then fit it properly inside the menu-item
//13. Populate the dropdown-icon element with the help of CSS 
//    pseudo-elements
//14. Adjust the direction and dropdown-icon positioning for the 
//    right-to-left menu
//
.menu {

  // 1 //
  .menu-item {
    display: block;

    // 2 //
    &.has-children { 
      &, .menu-item {
        position: relative;
      }
    }

    // 3 //
    &:not(:last-child) {
      border-width: 0 0 1px;
    }

    // 4 //
    a {
      padding: $menu-link-padding;
      display: flex;
      justify-content: space-between;
    }
  }

  // 5 //
  .sub-menu {
    border-width: 1px 0 0;
    
    // 6 //
    visibility: hidden;
    opacity: 0;
    height: 0;
    width: 0;
    
    // 7 //
    transition: visibility .25s, opacity .25s ease-in-out;
    
    // 8 //
    .menu-item {
      visibility: hidden;
    }
  }
  
  // 9 //
  .menu-item {
    &:hover,
    &:active,
    &:focus {
      > .sub-menu {
        opacity: 1;
        height: auto;
        width: auto;
        
        // 10 //
        &,
        .menu-item {
          visibility: visible;
        }
      }
    }
    
    // 11 //
    > a {
      &:hover,
      &:active,
      &:focus {
        .dropdown-icon {
          &:after {
            transform: rotate(45deg);
          }
        }
      }
    }
  }
  
  .dropdown-icon {

    // 12 //
    padding: $menu-link-padding $menu-link-padding * 1.25;
    margin: -1em -1em -1em .5em;
    border-width: 0 0 0 1px;
    
    // 13 //
    &:after {
      content: "+";
      display: block;
      transition: transform .25s ease-in-out;
    }
  }
}

// 14 //
.nav--rtl {
  direction: rtl;
  unicode-bidi: bidi-override;
  
  .menu {
    .dropdown-icon {
      margin: -1em .5em -1em -1em;
      border-width: 0 1px 0 0;
    }
  }
}

Breakpoints

Now comes the crucial part, where we need to reconstruct our menu on bigger screens by adding a media query. Setting the display to flex for our menu link-list will make the menu-items aligned horizontally on bigger screen sizes.

We have also made our sub-menus break out of the flow to give them a look of dropping-down menus. First level sub-menus are dropping towards the bottom, and rest of the others are falling on the extreme sides of their parent sub-menus.

//
// Settings for different screens
//
// Breakpoints or media queries to make our navigation menu adapt according
// to the available screen width.
//
// 1. Make the top-level menu look horizontal on bigger screens
// 2. Adjust the seperating border for the top-level horizontal menu
// 3. Remove the left padding from the dropdown-icon in the top-level menu 
//    to maintain the proper whitespace.
// 4. Give fly-outs (sub-menus) a width on hovering, selecting, focusing 
//    of parent menu-item
// 5. Throw the sub-menus out of the flow to make them appear as fly-outs
// 6. Make the sub-menu start just from below the parent element
// 7. Zero-down the border-width as we don't really need separation here
// 8. This makes the second sub-menu fly-out on the top-right side of their 
//    parent sub-menu
// 9. Zero-down the border-width of the dropdown-icon
//10. Adjust the padding and spacing for the dropdown-icon in right-to-left 
//    navigation menu
//11. Align the sub-menu fly-outs from right to left instead of left to 
//    right
//12. Align the top-level menu to the center for bigger screens
// 
@media only screen and (min-width: 1024px) {
  .nav {
    // 1 //
    .menu {
      display: flex;
      flex-flow: row wrap;
      
      // 2 //
      > .menu-item {
        border-width: 0 1px 0 0;
        
        // 3 //
        > a {
          .dropdown-icon {
            padding-left: 0;
          }
        }
      }

      .menu-item {
        &:hover,
        &:active,
        &:focus {

          // 4 //
          > .sub-menu {
            width: 225px;
          }
        }
      }
      // 5 //
      .sub-menu {
        position: absolute;
        z-index: 3000;

        // 6 //
        top: 100%;
        left: 0;

        // 7 //
        border-width: 0;

        // 8 //
        .sub-menu {
          top: 0;
          left: 100%;
        }
      }
      
      // 9 //
      .dropdown-icon {
        border-width: 0;
        padding: $menu-link-padding;
      }
    }
  }
  
  .nav--rtl {
    .menu {
      > .menu-item {

        // 10 //
        > a {
          .dropdown-icon {
            padding-left: $menu-link-padding;
            padding-right: 0;
          }
        }
      }

      .sub-menu {

        // 11 //
        top: 100%;
        left: auto;
        right: 0;

        .sub-menu {
          left: auto;
          right: 100%;
        }
      }
    }
  }
  
  // 12 //
  .nav--cen {
    .menu {
      justify-content: center;
    }
  }
}

Theming

We may pimp up our menu more further by adding more transitions, colors, etc. depending on the theme, and the need for look and feel. Here is some Sass for you to quickly theme it up:

//
// Theming (applying cosmetics)
//
// 1. Background for the top-level menu
// 2. Border-color for the top-level menu and items
// 3. Text and link color for the top-level menu
// 4. Hover effects for the links in top-level menu
// 5. Background for the sub-menus
// 6. Border-color for the sub-menus and items
// 7. Text and link color for the sub-menus
// 8. Hover effects for the links in the sub-menus
//
.nav {
  // 1 //
  background-color: $menu-bg;

  .menu {
    > .menu-item {
      // 2 //
      &,
      .dropdown-icon {
        border-color: $menu-bo;
      }
    }
    > .menu-item {
      // 3 //
      &,
      & > a {
        color: $menu-co;
      }
      
      // 4 //

      & > a {
        &:hover,
        &:active,
        &:focus {
          background-color: $menu-bg-hover;
          color: $menu-co-hover;
        }
      }
    }
  }

  .sub-menu {
    // 5 //
    background-color: $sub-menu-bg;

    // 6 //
    &,
    .menu-item {
      &,
      .dropdown-icon {
        border-color: $sub-menu-bo;
      }
    }
    
    .menu-item {
      // 7 //
      &,
      a {
        color: $sub-menu-co;
      }
      
      // 8 //
      a {
        &:hover,
        &:active,
        &:focus {
          background-color: $sub-menu-bg-hover;
          color: $sub-menu-co-hover;
        }
      }
    }
  }
}

As you have already seen in the demonstrations, I’ve added a dropdown menu-icon too for the sub-menus’ parents, which transforms on hovering over the menu items. I’ll leave it up to you as an exercise to figure out how it is done.

Demo the CSS-only Dropdown Menu

That’s all about it. I tried to provide you the fully documented code in the tutorial above. I hope you found this one useful. Will be doing more stuff like this in future posts, stay tuned, and don’t forget to share your thoughts. Thanks for the read :)