Bulma: dropdown navbar does not close when clicking

Created on 2 Jun 2019  路  11Comments  路  Source: jgthms/bulma

This is about **Bulma

Overview of the problem

This is about the Bulma CSS framework
I'm using Bulma version [0.7.5]
My browser is: firefox and chromium

Description

I have made a navbar with dropdown
The dropdown menus hover fine. The dropdown unfolds fine when clicked upon (touchscreen laptop and tablet). But it often does not fold back when clicked twice.

Steps to Reproduce

  1. go to website code here for bulma 0.7.4
    or wesite with code here for bulma 0.7.5
  2. click on all three dropdowns
  3. click again

Expected behavior

when you click twice dropdown should close again

Actual behavior

it does not close, or sometimes the wrong dropdown closes

I hope I did not make some coding mistake, but I am no pro.

Most helpful comment

My guess is that it comes from the router. I had the same issue with my Vue app and I also use Vue router, but I found a way to fix it.

EDIT : I tried to remove the focus, using element.blur() in javascript, or to put the focus somewhere else, but it was not working.

About the issue

When you click on a dropdown link, it seems like the css class .navbar-item .is-hoverable:focus-within .navbar-dropdown is triggered, it has display: block inside. Looks like we have the focus on one of our .navbar-item inside the dropdown once we clicked on it, even after the view has been loaded and displayed. By removing this class in the Bulma navbar's source file I could see that things were working correctly again, but it's not a good solution as you can guess, because it's not how you handle it in a project where you're not alone, and if you keep bulma up to date, reinstall your project....etc. I did it this way just so checking was more easily, but below I'll explain how I finally fixed this issue.

How to fix it

You'd be tempted just to override .navbar-item.is-hoverable:hover .navbar-dropdown in your components or in your style file, but you will see that when you click on a link in the dropdown, the whole dropdown disappears and will not be displayed anymore except if you go to another route in your app, which is not a good behavior. You'd like to see it again on hover. Also, you don't want your links to disappear on small screens _(and you can't trigger the :hover pseudo-class on small screens that is used to display them again)_.

With all of that in mind, I came with the following code in my style.scss file (overwriting the original Bulma code, then) :

@media screen and (min-width: 1025px) {
    .navbar-item.is-hoverable:hover .navbar-dropdown {
        display: block !important;
    }
    .navbar-item.is-hoverable:focus-within .navbar-dropdown {
        display: none;
    }
}

As you can see I added !important for the hover part because :focus-within will make it disappear before the click event is triggered. The :focus-within pseudo-class is triggered by the _mousedown_ event while a click (on a link here) is triggered after a _mousedown / mouseup_ events, so you obviously won't be able to browse in your website if you can't click on your links.

For small screens you just need to remove the is-active class from your .navbar-menu and .navbar-burger using javascript when you click on a link. You can do it in the mounted hook of the component lifecycle for instance. Something like this will work :

mounted: function () {
    const links = menu.querySelectorAll(".navbar-item");
    links.forEach(link => {
        link.addEventListener("click", function () {
            burger.classList.remove("is-active");
            menu.classList.remove("is-active");
        });
    });
}

_Of course I guess that you already have the necessary code to toggle the is-active class for the burger_

All 11 comments

I have the same problem.

I'm using VueJs 2.5.17 and Buefy 0.7.10, so that represents Bulma 0.7.5

Description

My Navbar has a dropdown, containing a router-link. Hovering the dropdown opens and closes it, as it should be. But when I click the link, the dropwdown stays open and I need to click somewhere in my page to close it.

    <div class="navbar-menu">
      <!-- navbar start, navbar end -->
      <div class="navbar-start">
        <div class="navbar-item">
          <b-input placeholder="search" size="is-small" rounded v-model="searchField"></b-input>
        </div>
        <a class="navbar-item" href="#!">
          <b-icon icon="magnify" size="is-small"></b-icon>
        </a>
      </div>
      <div class="navbar-end">
        <div class="navbar-item has-dropdown is-hoverable">
          <a class="navbar-link">
            <b-icon icon="account-circle-outline" size="is-small"></b-icon>
          </a>
          <div class="navbar-dropdown is-right is-boxed">
            <div>
              <router-link class="navbar-item has-text-weight-semibold" to="/profile">{{fullname}}</router-link>
              <p class="navbar-item is-family-code">{{username}}</p>
            </div>
            <hr class="navbar-divider">
            <a class="navbar-item" @click="logout">Log out</a>
          </div>
        </div>
      </div>
    </div>

Expected behavior

Dropdown should close after a click/navigating

My guess is that it comes from the router. I had the same issue with my Vue app and I also use Vue router, but I found a way to fix it.

EDIT : I tried to remove the focus, using element.blur() in javascript, or to put the focus somewhere else, but it was not working.

About the issue

When you click on a dropdown link, it seems like the css class .navbar-item .is-hoverable:focus-within .navbar-dropdown is triggered, it has display: block inside. Looks like we have the focus on one of our .navbar-item inside the dropdown once we clicked on it, even after the view has been loaded and displayed. By removing this class in the Bulma navbar's source file I could see that things were working correctly again, but it's not a good solution as you can guess, because it's not how you handle it in a project where you're not alone, and if you keep bulma up to date, reinstall your project....etc. I did it this way just so checking was more easily, but below I'll explain how I finally fixed this issue.

How to fix it

You'd be tempted just to override .navbar-item.is-hoverable:hover .navbar-dropdown in your components or in your style file, but you will see that when you click on a link in the dropdown, the whole dropdown disappears and will not be displayed anymore except if you go to another route in your app, which is not a good behavior. You'd like to see it again on hover. Also, you don't want your links to disappear on small screens _(and you can't trigger the :hover pseudo-class on small screens that is used to display them again)_.

With all of that in mind, I came with the following code in my style.scss file (overwriting the original Bulma code, then) :

@media screen and (min-width: 1025px) {
    .navbar-item.is-hoverable:hover .navbar-dropdown {
        display: block !important;
    }
    .navbar-item.is-hoverable:focus-within .navbar-dropdown {
        display: none;
    }
}

As you can see I added !important for the hover part because :focus-within will make it disappear before the click event is triggered. The :focus-within pseudo-class is triggered by the _mousedown_ event while a click (on a link here) is triggered after a _mousedown / mouseup_ events, so you obviously won't be able to browse in your website if you can't click on your links.

For small screens you just need to remove the is-active class from your .navbar-menu and .navbar-burger using javascript when you click on a link. You can do it in the mounted hook of the component lifecycle for instance. Something like this will work :

mounted: function () {
    const links = menu.querySelectorAll(".navbar-item");
    links.forEach(link => {
        link.addEventListener("click", function () {
            burger.classList.remove("is-active");
            menu.classList.remove("is-active");
        });
    });
}

_Of course I guess that you already have the necessary code to toggle the is-active class for the burger_

I have this same problem with buefy 0.7.8.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Is there going to be a fix implemented for this in Bulma or is the above workaround the way to go?

I have same problem with vue.js and vue-router, where the drowp down does not close in my SPA. I tried a differnt tag for router-links which resolved the close problem.

<router-link tag="li" ..>

So when you style the navbar with

    However you will hit into #1307 where navbar-items are not styled properly .

    toggleMenu(e) {
      let menu = e.currentTarget.querySelector(".navbar-dropdown");
      if(e.target.parentElement.classList.contains("navbar-dropdown"))
      menu.style.display = "none";
      setTimeout(()=>{ 
        menu.style.display = "";
        e.target.blur();
      },100);
    }

Try adding this method to click on every parent _navbar-item_, don't forget to pass the event

I use this when I encounter a similar issue when using react-router-dom

import { useHistory } from "react-router-dom";

...

const { listen } = useHistory();

React.useEffect(
  () =>
    listen(() => {
      const el = document.activeElement as HTMLElement;

      el?.classList?.contains("navbar-item") && el.blur();
    }),
  [listen]
);

I don't think this is an issue on the CSS library, but here is one way to hide the dropdown or navbar-dropdown after a click using Vuejs.

See: https://gist.github.com/adambouchard/1e265f780b451546a231d7b0ff76b84e

Steps

1) Set a "ref" on the dropdown

2) add a method to replace the class without is-hoverable (this clears the hover), then use a setTimout to add it back.

   hideHover() {
      let e = this.$refs.dropdown
      e.className = "dropdown";
      setTimeout(function () {
        e.className = "dropdown is-hoverable";
      }, 100);
    }

In my case (React, Next.js) the problem was that the header / menu component was reused on different pages.

I solved it by adding a key-prop on the div with the class "navbar-item" that contains the dropdown. The value of the key prop is the route of the current page. That means the dropdown is recreated when changing the page, and therefore the dropdown does not stay opened anymore.

For mobile, I solved it by setting the menu to closed every time the route changes.
No CSS changes needed in that case.

Hope this helps someone in a similar situation.

Thanks @ka-raph and @corescript - here's my code that combines both your approaches to solve the problem in an automated fashion, no v-on:click function needed:

mounted () {
  // Add a listener to all dropdown menus to hide them after clicking an item - otherwise the menu stays open
  document.getElementById('NAVBAR_ID')
    .querySelectorAll('.has-dropdown')
    .forEach(el => {
      el.addEventListener('click', () => {
        let menu = el.querySelector('.navbar-dropdown')
        menu.style.display = 'none'
        setTimeout(() => {
          el.blur()
          // Reset the display property to its original state, so the menu can appear again next time
          menu.style.display = ''
        }, 200)
      })
    })
}
Was this page helpful?
0 / 5 - 0 ratings