Hover submenus in Source

I wanted dropdown submenus for Source, similar to what is accomplished with Add Dropdowns to Main Menu Through the Site Admin (Copy & Paste into Site Header/Footer Injections)

That code relies on jQuery, which is not included in Source, and does not take into account that Source collapses menu items into a dropdown on its own.

This code should be put into your header injections

<style>
li.menu-item-has-children {
    position: relative;
    padding-right: 15px!important;
    display: inline;
}

.menu-item-has-children svg {
    position: absolute;
    right: 0px;
    top: 35%;
    transform: scale(1.1);
}

div.gh-dropdown {
    right: -100px;
    margin-top: 24px;
    top: 0px;
    border-bottom: 3px solid var(--ghost-accent-color);
}
    
li.menu-item-has-children:hover div.gh-dropdown, div.gh-dropdown:hover {
    opacity: 1;
    transform: translateY(0);
    visibility: visible;
}

div.gh-dropdown ul {
    padding: 0;
}

div.gh-dropdown li {
    list-style: none;
    white-space: nowrap;
}

div.gh-dropdown li:hover {
    background-color: var(--ghost-accent-color);
}

div.gh-dropdown li:hover a {
    color: var(--color-darker-gray)!important;
    opacity: 0.8;
}
</style>

This goes into footer injections:

<script>
    "use strict";
    function ghost_dropdown(options) {
       let defultOptions = {
            targetElement: ".gh-navigation-menu ul li",
            hasChildrenClasses: "menu-item-has-children",
            hasChildrenIcon: "<svg xmlns='http://www.w3.org/2000/svg' width='11' height='7' fill='currentColor' class='bi bi-caret-down' viewBox='0 0 11 7'><path d='M5.4999 6.20003L0.649902 1.35003L1.3499 0.650024L5.4999 4.80002L9.6499 0.650024L10.3499 1.35003L5.4999 6.20003Z'/></svg>",
            hasChildDetectText: "[has_child]",
            submenuUlClasses: "gh-dropdown",
            subitemDetectText: "[subitem]",
            subitemLiClasses: "subitem"
        }
        options = {
            ...defultOptions,
            ...options
        }
        // Target Element
        let targetElement = options.targetElement
        let hasChildrenClasses = options.hasChildrenClasses
        let hasChildrenIcon = options.hasChildrenIcon
        let hasChildDetectText = options.hasChildDetectText
        let submenuUlClasses = options.submenuUlClasses
        let subitemDetectText = options.subitemDetectText
        let subitemLiClasses = options.subitemLiClasses

        let navigation = document.querySelector('#gh-navigation')

        // This bit can be removed if used for themes other than Source
        if (navigation?.classList?.contains('is-dropdown-loaded')) {
          navigation.classList.remove('is-dropdown-mega')
          let menu = navigation.querySelector('nav.gh-navigation-menu')
          let ul = menu?.querySelector('ul.nav')
          let button = ul?.querySelector('button.gh-more-toggle')
          if (button) {
            ul.removeChild(button)
            button.querySelector('div.gh-dropdown')?.querySelectorAll('li').forEach(function (element, index) {
              element.parentElement.removeChild(element)
              ul.appendChild(element)
            })
          }
        }

        let parentEl = document.querySelectorAll(targetElement)
        let parentIndex = []

        parentEl.forEach(function (element, index) {
          element.parentElement.classList.add('ghost-dropdown-menu')
          if (element.textContent.indexOf(hasChildDetectText) >= 0) {
                parentIndex.push(index)
                element.innerHTML = element.innerHTML.replaceAll(hasChildDetectText, '')
                element.classList.add(hasChildrenClasses)
                element.innerHTML = element.innerHTML + hasChildrenIcon + (`<div class='${submenuUlClasses}'><ul></ul></div>`)
                element.style.opacity = '1'
            }
        })
        parentIndex.forEach(function (i) {
          let collecting = true
          parentEl.forEach(function (element, index) {
            if (index > i) {
              if (element.textContent.indexOf(subitemDetectText) < 0) {
                collecting = false
              }
              if (collecting) {
                element.innerHTML = element.innerHTML.replaceAll(subitemDetectText, '')
                element.classList.add(subitemLiClasses)
                element.parentElement.removeChild(element)
                parentEl[i].querySelector(`div.${submenuUlClasses}`).querySelector('ul').appendChild(element)
              }
            }
          })
        })
    }
    window.addEventListener("load", function() {
       ghost_dropdown();
    })

    // This bit can be removed if used for themes other than Source
    window.addEventListener('resize', function () {
        setTimeout(() => {
           ghost_dropdown();
        }, 1);
    });
</script>

The code is fairly generic Javascript and should work for themes without external dependencies. The CSS relies on Source CSS for displaying the menu, so you likely need something like

<style>
.gh-dropdown {
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 0 1px rgba(0,0,0,.04),0 7px 20px -5px rgba(0,0,0,.15);
margin-top: 24px;
opacity: 0;
padding: 12px 0;
position: absolute;
right: -16px;
text-align: left;
top: 100%;
transform: translate3d(0,6px,0);
transition: opacity .3s,transform .2s;
visibility: hidden;
width: 200px;
z-index: 90;
}
</style>

before the rest of the styles if used with other themes.

Note that this will potentially cause problems if the main menu is so long after introducing submenus that the Source menu collapse script kicks in – the existing menu collapse is hard to disable, so I just undo its work, which works for short top menus only.

See original thread for usage; this is what the result looks like on my site:

1 Like

If you want to use a CDN, the below codes work with the theme Source - v1.2.3 on Ghost Version 5.85.0. Also works in Casper…

Site Header Injection…

<!-- Dropdown Menu -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rakihub/ghost-code-injection@main/nested-menu/built/nested-menu.min.css">

Site Footer Injection…

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
## The below codes work for latest Ghost default theme source - v1.2.3

<!-- Dropdown Menu -->
<script src="https://cdn.jsdelivr.net/gh/rakihub/ghost-code-injection@main/nested-menu/built/nested-menu.min.js"></script>

Navigation…

Thank you, @denvergeeks, for sharing my code solution in the community. But there is a minor error in your post:
for " Site Footer Injection…", there is no need to add jQuery. Simply add the below code is enough:

<!-- Dropdown Menu -->
<script src="https://cdn.jsdelivr.net/gh/rakihub/ghost-code-injection@main/nested-menu/built/nested-menu.min.js"></script>

Hello there,
I’m using the code above to my Source theme, it works, however since my dropdown menu is positioned in the middle of my navigation menu it overlays with the following primary links, in mobile view.

Any ideas to overcome this design problem?

thanks in advance!

1 Like

Hi @Renato_Dias_Music. Mine does the same thing. Looks like you have to pay @Raki for this to work fully. Would be nice if Ghost would simply release this as a feature since it’s been requested for years now.