Custom Drop Down Menu with click outside close

In this post I’ll demonstrate how to create a custom drop down menu, that has the added bonus of closing when you click outside of the menu.

If you just want to see the custom drop down menu working with click outside. You can view source (everythings on the page including css and js)

Anyway on to the code the menu itself is as below, I’ve stored the option values in the data attributes.

    <div id="open-dropdown" style="border: 1px solid #D1D5DB; height:50px;" class="">
        down icon
    </div>
    <div id="dropdown-select" style="width: 400px;" class="p-2">
        <div class="menu-option" data-option-value="eng">
            English
        </div>
        <div class="menu-option" data-option-value="esp">
            Spanish
        </div>
        <div class="menu-option" data-option-value="fren">
            Frenchy
        </div>
    </div>

I’ve kept the css basic for the dropdown too, you could get rid of some of it. The main things are:

position: absolute ( so the menu is out of document flow and can over lap other elements without pushing them down).

display: none ( so the simple custom menu is closed by default ).

z-index: if your menu is hidden behind other things when open, bring the z-index up.

    <style>
        #dropdown-select {
            display: none;
            position: absolute;
            background-color: #f9f9f9;
            min-width: 160px;
            box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
            z-index: 1;
        }
    </style>

Next on to the fun bit the javascript to open and close the menu, record the users choice when selecting from the menu and clicking outside to close it.

        addEventListener("DOMContentLoaded", () => {
            addListenerOpenDropDown();
            document.addEventListener('click', closeSkillsDropdownIfClickedOutside);
            addListenersToOptions();
        });

        function addListenerOpenDropDown() {

            const openMultiSkills = document.getElementById("open-dropdown");
            openMultiSkills.addEventListener('click', function (event) {
                event.preventDefault();
                document.getElementById("dropdown-select").style.display = 'block';
            });

        }


        function closeSkillsDropdownIfClickedOutside() {
            const multiSkillsDropdown = document.querySelector('#dropdown-select');

            if (multiSkillsDropdown.style.display !== 'block') {
                return;
            }

            const withinBoundaries = event.composedPath().includes(multiSkillsDropdown);
            const triggerMultiSelectOpener = document.getElementById("open-dropdown");
            const withinBoundariesOfTrigger = event.composedPath().includes(triggerMultiSelectOpener);

            if (!withinBoundaries && !withinBoundariesOfTrigger) {
                multiSkillsDropdown.style.display = 'none'
            }
        }

        function addListenersToOptions() {
            const nodeList = document.querySelectorAll(".menu-option");

            [...nodeList].forEach(menuOption => {

                menuOption.addEventListener('click', function (event) {
                    event.preventDefault();
                    alert("option value = " + menuOption.dataset.optionValue);
                    const multiSkillsDropdown = document.querySelector('#dropdown-select');
                    multiSkillsDropdown.style.display = 'none';

                });

            });
        }

I’ve keep the actual menu, really simple bare bones / hello world type custom drop down menu. As I want demo that and not bloat it with loads of different features ( I’ve not even put a down / chevron graphic into it).

When the page loads (DOMContentLoaded) I add the listeners:

  1. addListenerOpenDropDown for opening and closing the menu
  2. closeSkillsDropdownIfClickedOutside , for closing the menu if the user clicks outside
  3. addListenersToOptions() , to record the users choice when clicking a menu item.

There are many ways to do the click outside, I’ve used the stop propagation technique in the past, but these days the composedPath way seems to be the way to go, you can read the various thoughts and debate about this on stackoverflow if you google something like ‘how to build simple custom select box with click away functionality’.

I won’t dig into it too much, as I think the codes self explanatory, but comment if you’d like more detail and I’ll elaborate on anything, you’d like me to expand on.

Leave a Comment