Navigation / Dropdown Double

Description of component:

ClickUp Ticket


How to use this component

This component is a rich navigation that includes dropdown options, commonly used for the main navigation of a website.

This component has been coded to be reusabled, meaning there can be more than one on a page.

Note: This does come with the note that the nav's ID needs to be unique. It also means that the if statement checking the containment of the document.activeElement needs to be adjusted to include all applicable ID targets.

This component requires a aria-label="" and should be a contextual nav name

Why list-style="";? https://www.matuzo.at/blog/2023/removing-list-styles-without-affecting-semantics


Additional Features to improve UX


Component Example


Code Snippets

HTML

                
<nav aria-label="Main Navigation" class="dropdown-nav" id="main-nav">
    <ul>
        <li class="has-submenu">
            <button class="js--sub-menu-trigger sub-menu-trigger" aria-haspopup="true" aria-expanded="false">Navs</button>
            <ul>
                <li>
                    <button class="js--sub-sub-menu-trigger sub-sub-menu-trigger" aria-haspopup="true" aria-expanded="false">Nav Nested</button>
                    <ul>
                        <li><a class="js--sub-sub-link" href="/components/navigation/dot.html">Nested Dot</a></li>
                        <li><a class="js--sub-sub-link" href="/components/navigation/hamburger.html">Nested Hamburger</a></li>
                        <li><a class="js--sub-sub-link" href="/components/navigation/expanded.html">Nested Expanded</a></li>
                    </ul>
                </li>
                <li><a class="js--sub-link" href="/components/navigation/dot.html">Dot</a></li>
                <li><a class="js--sub-link" href="/components/navigation/hamburger.html">Hamburger</a></li>
                <li><a class="js--sub-link" href="/components/navigation/expanded.html">Expanded</a></li>
            </ul>
        </li>
        <li class="has-submenu">
            <button class="js--sub-menu-trigger sub-menu-trigger" aria-haspopup="true" aria-expanded="false">Links</button>
            <ul>
                <li><a class="js--sub-link" href="/components/links">Links Home</a></li>
                <li><a class="js--sub-link" href="/components/links/back-to-top.html">Back to Top</a></li>
                <li><a class="js--sub-link" href="/components/links/breadcrumbs.html">Breadcrumbs</a></li>
                <li><a class="js--sub-link" href="/components/links/skip-content.html">Skip Content</a></li>
            </ul>
        </li>
        <li class="has-submenu">
            <button class="js--sub-menu-trigger sub-menu-trigger" aria-haspopup="true" aria-expanded="false">Buttons</button>
            <ul>
                <li><a class="js--sub-link" href="/components/buttons">Buttons Home</a></li>
                <li><a class="js--sub-link" href="/components/buttons/pause-animations.html">Pause Animations</a></li>
            </ul>
        </li>
    </ul>
</nav>
                
            

CSS

                
.dropdown-nav {
    >ul {
        display: flex;
        padding: 0;
        list-style: "";
        
        >li {
            margin-right: 1rem;
            position: relative;
            
            &:last-child {
                margin-right: 0;;
            }
            
            >ul {
                display: none;
                position: absolute;
                padding: 0;
                list-style: "";
                
                >li {
                    margin-right: 1rem;
                    position: relative;
                    
                    &:last-child {
                        margin-right: 0;;
                    }
                    
                    >ul {
                        display: none;
                    }
                }
            }
            
            .sub-menu-trigger, .sub-sub-menu-trigger  {
                position: relative;
                padding-right: 1rem;
                
                &::before, &::after {
                    content: '';
                    display: block;
                    width: 7px;
                    height: 2px;
                    background-color: black;
                    position: absolute;
                }
                
                &::before {
                    top: 50%;
                    right: 6px;
                    transform: translateY(-50%) rotate(45deg);
                }
                
                &::after {
                    top: 50%;
                    right: 2px;
                    transform: translateY(-50%) rotate(-45deg);
                }
                
                &[aria-expanded="true"] {
                    &::before {
                        transform: translateY(-50%) rotate(-45deg);
                    }
                    
                    &::after {
                        transform: translateY(-50%) rotate(45deg);
                    }
                }
            }
            
            .sub-menu-trigger[aria-expanded="true"] + ul, .sub-sub-menu-trigger[aria-expanded="true"] + ul  {
                display: block;
            }
        }
    }
}
                
            

JS

                
// the following code is found in /src/scripts/globals/subNavs.js
//set empty vars for loops
var i;
var x;


/* subNavs(triggerClass)
* This function opens/closes the different sub/sub-sub navs
* It takes in the class of the trigger
* - - in this cases that is js--sub-menu-trigger and js--sub-sub-menu-trigger
============================================= */
function subNavs(triggerClass) {
    var triggers = document.querySelectorAll('.' + triggerClass);
    
    for(i = 0; i < triggers.length; i++) {
        triggers[i].setAttribute('id', triggerClass + '-' + (i + 1));
        
        triggers[i].addEventListener('focus', function(){
            for(x = 0; x < triggers.length; x++) {
                triggers[x].setAttribute('aria-expanded', 'false');
                triggers[x].classList.remove('is-open-nav');
            }
        });
        
        triggers[i].addEventListener('click', function(){
            this.classList.toggle('is-open-nav');
            
            if( this.classList.contains('is-open-nav') ) {
                this.setAttribute('aria-expanded', 'true'); 
            } else {
                this.setAttribute('aria-expanded', 'false');
            }
        });
    }
    
}

subNavs('js--sub-menu-trigger');
subNavs('js--sub-sub-menu-trigger');







/* Ensure the nav dropdowns close when the main nav is not in focus
* First grab the main nav id
* Create closeSubSubNav(triggerClass)
* * this function takes the trigger class
* * – – in this case it is using 'js--sub-menu-trigger'
* * this function pulls a for loop to ensure all subs are closed
* closeSubSubNav(triggerClass)
* * this function takes the trigger class
* * – – in this case it is using 'js--sub-sub-menu-trigger'
* * this function queries all triggerClass eles, and if they contain the class 'is-open-nav' it grabs the id of the open ele and resets the attrs to have it close
============================================= */
function resetSubNav(triggerClass) {
    var triggers = document.querySelectorAll('.' + triggerClass);
    for(x = 0; x < triggers.length; x++) {
        triggers[x].setAttribute('aria-expanded', 'false');
        triggers[x].classList.remove('is-open-nav');
    }
}

function closeSubSubNav(triggerClass) {
    var triggers = document.querySelectorAll('.' + triggerClass);
    for(x = 0; x < triggers.length; x++) {
        if( triggers[x].classList.contains('is-open-nav') ) {
            var subSubId = triggers[x].getAttribute('id');
            var subSubNav = document.getElementById(subSubId);
            var subSubNavParent = document.getElementById(subSubId).parentElement;
            
            if( !subSubNavParent.contains(document.activeElement) ) {
                subSubNav.setAttribute('aria-expanded', 'false');
                subSubNav.classList.remove('is-open-nav');
            }
        }
    }
}






// the following code is found in src/scripts/globals/keydown.js
/* Window Keydown Event
* contain function calls in timeout to ensure the event is calling the correct eles
============================================= */
var subNavResetTrigger = '';
var mainNavEle = document.getElementById('main-nav');

window.addEventListener("keydown", function(e){
    if (e.keyCode == 9 || e.keyCode == 40 || e.keyCode == 39 || e.keyCode == 38 || e.keyCode == 37) {
        
        if (isTabbing !== null) {
            window.clearTimeout(isTabbing);        
        }
        
        var isTabbing = setTimeout(function() {
            /*
                Check Nav Element for focus
                * if contains focus trigger closeSubSubNav function and set trigger var to trigger
                * else check if trigger is set and active element is not in targetted nav and call resetSubNav()
            ============================================= */
            if( mainNavEle.contains(document.activeElement) ) {
                closeSubSubNav('js--sub-sub-menu-trigger');
                subNavResetTrigger = 'trigger';
            } else if ( subNavResetTrigger == 'trigger' ) {
                if( !mainNavEle.contains(document.activeElement) ) {
                    resetSubNav('js--sub-menu-trigger');
                    subNavResetTrigger = '';
                }
            }
        }, 25);
    }
});
                
            

JS Adjustments for Multi-Navs

                
var mainNavEle = document.getElementById('main-nav');
var mainNavEle2 = document.getElementById('main-nav2');

/*  Example of a single nav item on the page
============================================= */
if( mainNavEle.contains(document.activeElement) ) {
    closeSubSubNav('js--sub-sub-menu-trigger');
    subNavResetTrigger = 'trigger';
} else if ( subNavResetTrigger == 'trigger' ) {
    if( !mainNavEle.contains(document.activeElement) ) {
        resetSubNav('js--sub-menu-trigger');
        subNavResetTrigger = '';
    }
}


/*  Example of two+ nav items on the page
============================================= */
if( mainNavEle.contains(document.activeElement) || mainNavEle2.contains(document.activeElement) ) {
    closeSubSubNav('js--sub-sub-menu-trigger');
    subNavResetTrigger = 'trigger';
} else if ( subNavResetTrigger == 'trigger' ) {
    if( !mainNavEle.contains(document.activeElement) || mainNavEle2.contains(document.activeElement) ) {
        resetSubNav('js--sub-menu-trigger');
        subNavResetTrigger = '';
    }
}
                
            

Credits

inspired by https://www.w3.org/WAI/tutorials/menus/flyout/#use-parent-as-toggle