Mobile Nav


Web Development

Updated Oct 14th, 2021

I struggled for a while with mobile navs. Mostly because you really need to know how to target CSS as there’s nesting of lists within lists for submenu dropdowns. Animating a single level menu is fairly easy but to toggle a submenu you have target siblings and parent elements. I still think mobile nav and forms are some of the more difficult fundamentals you really should learn well.

Bootstrap

Bootstrap is the easiest solution by just pasting in code but a web developer needs to know how to code such basic web functionality from scratch!

Design Considerations

There’s design considerations too: should you animate from the top or from the side? Should a mobile menu take up the entire window? If it doesn’t take up the entire window does the content behind it stay fixed or get shifted down?

Multiple way to accomplish the same thing. Also, do you use JavaScript when CSS and HTML can do the trick? If you go with just CSS do you use the checkbox strategy event though it seems pretty “hacky”? What’s the best way for screen readers and accessibility?

Using CSS transform: scale as your animating property: This works, will want to use in tandem with visibility property as well.

Toggling Opacity: Setting an opacity to zero does not collapse the div like display none.

You really need to know your HTML structure and classes because you’re going to need to dig inside UL elements to find LI elements that might have more UL elements that have more LI elements that have “a tags”. And then when you want to do something do you use the “a element” or do you use the LI element?

When you need an arrow denoting a submenu do you use an icon or the CSS border trick hack? How can you animate the arrow to change direction?

My current strategy is to use a font awesome icon in the “::after” psuedo element for the a tag

The Animation/Action/Toggling/Activating Menu

JS or CSS?

How to get nested items programmatically. A few example below.

const hamburger = document.querySelector(".hamburger")
const navLinks = document.querySelector(".navLinks")
const links = document.querySelectorAll(".navlinks li")

hamburger.addEventListener("click", () => {
  
  navLinks.classList.toggle("open")
  
  
  links.forEach(link => {
    link.classList.toggle("fade")
  })

})

Here is another look at some JS to select and toggle. This one is interesting as it uses JS to see if the device is smaller than 768 pixels and if so, and the link has children, then add an active class. Some nested conditional logic and DOM traversal, I like it. On mobile it expands any containing list when clicked. On desktop there is a flyout below and then to the right. This code can be seen in the “multi-level-nav-simple-example” project folder. The CSS transitions are awful. It’s crazy but this is the menu that became the basis for the WP ECP Menu.

const li = document.querySelectorAll('li.dropdown a')
const btn = document.querySelector('.nav-btn')
const nav = document.querySelector('ul.nav')

btn.addEventListener("click", e => {
  nav.classList.toggle("toggle")
})

li.forEach(each => {
  if (each.nextElementSibling !== null) {
    each.addEventListener("click", e => {
      if (window.innerWidth < 768) {
        e.target.parentElement.classList.toggle("active")
      }
    })
  }
})

WordPress

WordPress brings yet another consideration as the navigation menu is dynamic. This means you need to make sure if a list item becomes a parent list item that it automatically gets the on-click functionality for it’s submenu as needed. Adding a second level to you menu via the WP admin panel shouldn’t break your menu.

How deep can WordPress navs nest and is your menu prepared to handle three or four nested levels? It seems like WordPress can do seemingly infinite nesting, (I tested at least 5 levels deep in the dashboard)? On the interwebs it says not all themes support multi-layer nested menus and most do up to two layers. Is there a way to make that restriction in the WP admin UI so the user wouldn’t just do it anyway and break the page?

How can you target the link inside a list-item menu item that has children without selecting all the links with children? In other words when you expand one sub menu how do you not expand all sub menus without having to create custom classes manually or target specific ID’s?

This may have been the best tutorial I’ve seen yet as far as fundamentals. The styling is terrible and it’s not the most quality produced video but it shows how to build a menu that is nested multiple layers deep with vanilla JavaScript. He even links a codepen.

There’s another video here that says it can create multi-level nav menu for mobile and desktop with just CSS. Haven’t watched it yet but may give it a go. And the codepen.

Here is a good non-video article.

Here is a simple solution using flexbox and no floats. This is what I should implement.

Having logo, hamburger-menu and nav all in one container div: continue to struggle using anything other than display:none, display:block to transition and there is no animation available on that. Anytime I try something different I am left with a blank space of where the menu was and I can’t get rid of it without having a height which I am not interested in since I want to use flexbox and padding to center and space. Max-height set to zero gets me there and is able to be animated but the animation only works after initial open?

Putting the nav in a different div: this option works for mobile in that you can now animate

Full Screen

There is a separate posts on Full Screen Menus here.

Having a full screen mobile now this something I also need to learn to do. Top left 0, height and width 100%. Why not top left right bottom all zero? Saw another tutorial that used position absolute; top, left 0, 100% width and 100vh but I’m not certain 100vh is supported for all browsers.

Also If the nav is not full screen and you click off the nav it should automatically close the nav

Kevin Powell Video

Finally watched this Kevin Powell video and this cleared a few things up for me. This is the video in which my current menu’s are based on, (His CSS for nav menu not CSS to toggle. I still use JS for toggle).

He uses the CSS with psuedo class of checked checkbox trick without JS trick. Again, I still use the JS approach.

<!-- Logo goes here -->
<input type="checkbox" id="nav-toggle" class="nav-toggle">
<!-- nav goes here -->
<!-- then add a burger under nav element as a label element -->
<label for="nav-toggle" class="nav-toggle-label"><span></span></label>

He hide the nav as a starting and then target with “.nav-toggle:checked ~ nav” where the tilde denotes any preceding sibling of.

.nav-toggle:checked ~ nav {
  display: block;
}

But we don’t want a checkbox to toggle we want a burger. So we add a label. Because the “for” attribute for the label is linked to the id clicking the label will also trigger the checkbox. So now we can hide the checkbox with display none and although it won’t be seen it is still there and being clicked on.

He then uses the label to position absolutely with an empty span with ::before and ::after pseudo elements displayed as block to accomplish the “burger from scratch.”

Okay so forget the toggle. This tutorial has a fixed header with high z-index.

Reiterated styles should be mobile first so media queries should be:

@media and screen (min-width: 768px) {
  .whatevs{}
}

The most important concept discussed was that transitions work best on scale, opacity, transform, and rotation for performance reasons. He hid and showed the mobile menu using the scale property. I reworked the HFRR theme nav and it worked perfectly going from scale(1, 0) to scale (1,1).

Another great tip was the animation delay for the links.

transition: opacity 250ms ease-in-out 300ms;

Where the 300ms is an animation delay which can be helpful for delaying a link opacity transition until the “ul” has become visible. He also mentioned what I’ve noticed before that sometimes animations will only be in one direction so you may need a transition on both blocks to say close a menu faster than you open it.

Also showed cool “line on hover effect” for nav links using the ::before and absolute positioning.

He also said that with SASS you don’t have to go all in you can pick and choose what you like.

They have Emmett snippets for CSS (m0, p2r, w100p).

Shows the use of “unset” to avoid having to re-declare or undo CSS properties for media queries.

React and Next.js

As I went through the videos listed below I quickly realized the only difference between this and vanilla JavaScript is the event handling and how to conditionally render classes based on the event. It is absolutely still just CSS that makes the nav bar responsive given a clicked or unclicked state.

Video one here using CRA, styled components, framer motion.

Max has a video of course!

React sidebar with Brian design here. He builds this in create react app. He adds a components folder and inside he has a navbar folder and a button component. Inside the bathtub photos is that a menu item component a nav bar component and a nav bar CSS file. In the nav bar CSS file he is returning a HTML nav element with a className navbar items. He has an H1 with className navbar-logo. I feel logo he creates a div with a className of menu-icon. Within this day if he has a hamburger menu icon (We’ll need to install font awesome). After the menu icon div he has a UL with a bunch of LIs inside. In the menu items component He keeps all of the menu items in an array ad objects. Each object has title, URL, and cName keys. He added a different C name of Nav-links-mobile for the object with title of sign up. Export menu item and import it into Navbar component. In the URL he needs to map over the array from menuItems and rigs up the props with curly brackets like href={}. Added a key to li as needed by react. brought in font awesome via cdn (index.html in CRA, not so in NEXT). Added google fonts using link syntax in index.html file. In navbar.css he styled things up. Added navbar to app.js file to actually show on screen. Back in Navbar he set up state and started it off as false. Added an onClick to the menu-icon and set to this.handleClick. On the icon “i” tag itself you put a className that used ternsry. This. state. clicked ? ‘x icon’ : ‘burger icon’. Create a handle click function. Inside he uses this.setState and in curly braces sets clicked to !this.state.clicked. As a quick aside this is finally when the real magic is starting to happen and it’s 33 minutes into the video. Now he goes into the UL and will conditionally render some css classes. In adding some more CSS he added a nav-link-mobile class, which corresponds to the value set of the same name in the menuItem component file. 49 minutes in is where most for the action happens with media query and event function. He started wide and put mobile styles in media. I think this is a mistake and should be mobile first. His media query is @media screen and (max-width: 960px). For the .nav-menu made height 500px, still curious on the best way to fill the screen. Set top to 80px since that is the height of the navbar. Animated using left property so set initial to -100%. He used a .nav-menu.active selector to reset left to zero and opacity to 1. He used translate to reposition the logo a bit. He had two separate signup buttons and hid the one in the nav by targeting the component itself to be display: none.

Coders never quit, here, shows using next.js ecosystem. IT’S NOT MOBILE AND USED A CDN! Components folder with NavBar.js inside. Created a layout folder and inside created a layout comp. Imported and rendered navbar. Have layout component take in and render children and curly brackets. In_app.js import layout and rap the component… Page props line in layout tags. In layout.js Add materialize CSS.com CDN by putting a link to the style sheet in the head tag after importing head from next head. Also put script in the bottom of the layout component. In navbar component paste in code from the CDN website and then import link from next link. Swap pay tags for link but actually just keep the A tags wrap the label in them. Had sign up login and create components so these are what show up in your navbar. Create is active function in navbar.js and then add on clicks to all of the li elements So when you click on one that’s the one that becomes active.

Fireship Responsive NavBar with CSS

The video is here. For the desktop view the menu is verticle and flys in from the side. On the mobile view it is horizontal and fixed to the bottom. When discussing icons he mentions font-awesome has new duotone icons with svg paths that have classes “fa-secondary” and “fa-primary” or you can also create your own duotone icons.

“You don’t need Bootstrap” is the opening line…

This menu does not have a mobile toggle but instead uses a small menu on mobile with no logo.

The structure of the menu is pretty basic nav > ul > li > a > svg & span

Note that on the desktop vertical menu the last item is pushed to the very bottom of the column. We achieve this in flexbox by setting the margin of the last item to auto.

main {
  /*Leaves room for left navbar*/
  margin-left: 5rem;
  padding: 1rem;
}

.navbar {
  position: fixed;
  transition: width 200ms ease;
  bottom: 0;
  width: 100vw;
  height: 5rem;
}

.navbar-nav {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: row;
  align-items: center;
}

.nav-item {
  width: 100%;
}

.nav-item:last-child {
  margin-top: auto;
}

.nav-link {
  display: flex;
  align-items: center;
  height: 5rem;
  filter: grayscale(100%) opacity(0.7);
}

.nav-link:hover {
  filter: grayscale(0%) opacity(1);
  /* CHange bg and color as well*/
}

.link-text {
  display: none;
  margin-left: : 1rem;
}

.nav-link svg {
  min-width: 2rem;
  margin: 0 1.5rem;
}

.navbar:hover {
  /* width: 16rem; */
}

.navbar:hover .link-text {
  /* display: block; */
}

.fa-primary {
  color: var(--primary);
}

.fa-secondary {
  color: var(--secondary);
}

.fa-primary, .fa-secondary {
  transition: var(--transition-speed);
}

.logo {
  display: none;
  font-weight: bold;
  text-transform: uppercase;
  margin-bottom: 1rem;
  text-align: center;
  color: var(--text-secondary);
}

.logo svg {
  transform: rotate(0deg);
  transition: transform var(--transition-speed);
}

.navbar:hover .logo svg {
  transform: rotate(-180deg);
}


@media only screen and (min-width: 600px) {
  .navbar {
    top: 0;
    width: 5rem;
    height: 100vh;
  }

  .navbar:hover {
    width: 16rem;
  }

  .navbar:hover .link-text {
    display: inline;
    transition: opacity var(--transition-speed);
  }

  .logo {
    display: block;
  }

  .navbar-nav {
    flex-direction: column;
  }

  .nav-link {
    justify-content: center;
  }

  main {
    margin: 0;
  }
}

/* CUSTOM BROWSER SCROLLBAR */

body::-webkit-scrollbar {
  width: 0.5rem;
}
body::-webkit-scrollbar-track {
  background: #222;
}
body::-webkit-scrollbar-thumb {
  background: blueviolet;
}

Top