Better Bootstrap Dropdowns

Accessible dropdowns for menus, select boxes and menu buttons that just work no matter if it's mouseover, keyboard navigation, screen reader or mobile. Try the menus above and the select boxes and buttons below. 👇

Introduction

If you're like me you're probably a little tired of the default Bootstrap look that your amazing back-end developer colleagues like to use thick and fast 👋. I've come to a point in my life where I needed to spice it up a little 🎉, so I made these dropdowns for them, but also for you...

It's not perfect code in any way, but works for me, and I've focused to make it as accessible as I can. Should work just as well for mouse/hover, touch, keyboard and screen reader use... and actually without Bootstrap as well 😱. I want to learn and get better though, so please let me know (via GitHub issues e.g.) if you disagree with something.

Before we get started I just want to clarify that "better" in this case means better for me, not necessarily for you... let's go! 🚗

After you've included bbdropdowns.js and bbdropdowns.css in your project, the script expects the following markup when using a dropdown menu, e.g. in your navbar:

<ul class="navbar-nav mr-auto bb-dropdowns">
  <li class="nav-item">
    <a href="#" class="nav-link">Menu one</a>
    <ul>
      <li>
        <a href="/link-1">Dropdown 1</a>
      </li>
      <li>
        <a href="/link-2">Dropdown 2</a>
      </li>
      <!-- ... more dropdown items -->
    </ul>
  </li>
  <!-- ... more menu items -->
</ul>

Put a .bb-dropdowns class on a ul and then the submenu dropdown list next to the a link in each li

To make it into the actual dropdowns, you need to initialize the script like this:

var dropdowns = new BbDropdowns();
dropdowns.init();

After that's done the markup has been touched up a little to use the needed CSS and to support screen readers and keyboard navigation. Users can now navigate by tabbing to each menu, open it with ENTER, SPACE or ⬇. Once in the submenu, it supports ESC to close, ⬇ and ⬆ to navigate. ⬅ and ➡ close the menu and goes to the next one if there is one available.

Add data-clickonly="true" to the menu item link disable hover and require click or tap to toggle the menu.

Select box

The following code example creates a select box after running .init(). Add data-select="true" to the first level link. The link text will act as a label for the select box. If any option should be pre-selected, put aria-checked="true" on that item.

<ul class="bb-dropdowns">
  <li>
    <a href="#" class="btn btn-danger" data-select="true">Select something</a>
    <ul>
      <li>
        <a href="#" data-value="value 1">Option one</a>
      </li>
      <li>
        <a href="#" data-value="value 2">Option two</a>
      </li>
    </ul>
  </li>
</ul>

Select boxes will emit a change event when the user selects a new value. You can subscribe to changes like you normally would with a select box:

document.getElemenyById('my-select').addEventListener('change', function() {
  console.log('The value is: ' + e.target.getAttribute('data-value'));
});
$('#my-select').change(function(e) {
  console.log('The value is: ' + e.target.getAttribute('data-value'));
});

Note that you need to use getAttribute('data-value') and not .value. See how the first and second select box in the initial examples work together.

Also note that this solution will never be as accessible as a real <select> box, but it aspires to be good enough for when you need the looks.

Menu button

Combining some markup and attributes from the previous two use cases of bbdropdowns.js you can easily create a menu button (or a group of buttons) opening a dropdown of links like the third button in the examples up top. The data-clickonly="true" attribute ensures it only opens by clicking, tapping or keypressing it.

<ul class="bb-dropdowns">
  <li>
    <a href="#" class="btn btn-info" data-clickonly="true">Select something</a>
    <ul>
      <li>
        <a href="/link-1">Link one</a>
      </li>
      <li>
        <a href="/link-2">Link two</a>
      </li>
    </ul>
  </li>
</ul>

Miscellaneous

Adding dynamically

If you want to add dropdowns in any form like above dynamically after the page has loaded, just insert the same markup anywhere and run the .init() function again to initialize any new dropdowns.

Updating dynamically

Select boxes can have it's dropdown items updated any time without having to run .init().

var selectbox = document.querySelector('#my-select + ul');
selectbox.innerHTML('<li><a href="#" data-value="example">Example option</a></li>');

Note that you can currently only change the inside options of the ul like this. If you need to change the main select button, remove and create a new one like above, along with any eventlisteners.

Unselect

If you need to reset a select box to it's initial unselected state, use .resetSelect(selector). Example:

dropdowns.resetSelect('#my-select');