Custom Multi-level Product Category Mega Menu Segment

This is a very specific solution that can easily be repurposed for similar designs if needed. The idea here is a mega menu that has multiple layers that relate to categories and their children to a maximum depth of 3 that outputs 4 columns into an Elementor mega menu.

  • The container generated by the mega menu needs to be set to full width and have zero margin and padding.
  • Directly inside this container is where the shortcode widget is placed.
  • All of the CSS for this was written with this setup in mind.
  • Subcategories, sub-subcategories and sub-sub-subcategories will always start hidden.
  • Selecting a category with a child will cause the next column to open up and reveal.
  • Categories without children act like normal links and link to their archives.
  • The background effect of each new column having a different background colour that bleeds off the right of the page is done with some CSS trickery with linear-gradients.
  • See the Screenshot of the design below.
PHP for functions.php:

The 2nd function that echos the JavaScript into the footer was done so because it is assumed that these shotcodes will always be used and loaded on every page on the front.

				
					function wc_custom_product_category_shortcode($atts) {
    // Shortcode attributes
    $atts = shortcode_atts(array(
        'parent_category' => 0,
    ), $atts, 'custom_product_categories');

    $parent_category_id = (int)$atts['parent_category'];

    // Get the direct children of the parent category
    $categories = wc_get_child_terms($parent_category_id);

    // If no categories found, return a message
    if (empty($categories) || is_wp_error($categories)) {
        return '<p>No child categories found.</p>';
    }

    // Start output (column 1: top-level child categories of the parent)
    $output  = '<div class="mega-category-list" data-depth="0">';
    $output .= '<div class="mega-category-list-wrapper">';
    $output .= '<div class="mega-cat-col-1">';
    $output .= '<ul class="custom-parent-categories">';

    // Build array of categories that do have children, to facilitate building column 2
    $sub_category_lists = array();

    foreach ($categories as $category) {
        $child_categories = wc_get_child_terms($category->term_id);
        
        // If the category has children, we store them for later rendering (column 2)
        if (!empty($child_categories) && !is_wp_error($child_categories)) {
            $output .= '<li data-slug="' . esc_attr($category->slug) . '">'
                     . '<a href="#">' . esc_html($category->name) . '</a></li>';
            $sub_category_lists[$category->term_id] = $child_categories;
        } else {
            // No children => direct link
            $output .= '<li data-slug="' . esc_attr($category->slug) . '">'
                     . '<a href="' . get_term_link($category) . '">' . esc_html($category->name) . '</a></li>';
        }
    }
    $output .= '</ul></div>';

    // Column 2: subcategories (children of the parent categories)
    $output .= '<div class="mega-cat-col-2">';
    $sub_sub_category_lists = array(); // Will store data for column 3

    foreach ($sub_category_lists as $parent_id => $child_terms) {
        // Get slug of the parent
        $parent_term = get_term($parent_id, 'product_cat');
        $parent_slug = $parent_term ? $parent_term->slug : '';

        $output .= '<ul class="sub-category-list" data-parent="' . esc_attr($parent_slug) . '">';
        foreach ($child_terms as $sub) {
            $sub_child_cats = wc_get_child_terms($sub->term_id);
            if (!empty($sub_child_cats) && !is_wp_error($sub_child_cats)) {
                $output .= '<li data-slug="' . esc_attr($sub->slug) . '">'
                         . '<a href="#">' . esc_html($sub->name) . '</a></li>';
                $sub_sub_category_lists[$sub->term_id] = $sub_child_cats; 
            } else {
                $output .= '<li data-slug="' . esc_attr($sub->slug) . '">'
                         . '<a href="' . get_term_link($sub) . '">' . esc_html($sub->name) . '</a></li>';
            }
        }
        $output .= '</ul>';
    }
    $output .= '</div>';

    // Column 3: sub-subcategories
    $output .= '<div class="mega-cat-col-3">';
    $sub_sub_sub_category_lists = array(); // For column 4

    foreach ($sub_sub_category_lists as $sub_id => $sub_sub_categories) {
        $sub_term  = get_term($sub_id, 'product_cat');
        $sub_slug  = $sub_term ? $sub_term->slug : '';

        $output .= '<ul class="sub-sub-category-list" data-parent="' . esc_attr($sub_slug) . '">';
        foreach ($sub_sub_categories as $sub_sub) {
            $sub_sub_children = wc_get_child_terms($sub_sub->term_id);
            if (!empty($sub_sub_children) && !is_wp_error($sub_sub_children)) {
                $output .= '<li data-slug="' . esc_attr($sub_sub->slug) . '">'
                         . '<a href="#">' . esc_html($sub_sub->name) . '</a></li>';
                $sub_sub_sub_category_lists[$sub_sub->term_id] = $sub_sub_children;
            } else {
                $output .= '<li data-slug="' . esc_attr($sub_sub->slug) . '">'
                         . '<a href="' . get_term_link($sub_sub) . '">' . esc_html($sub_sub->name) . '</a></li>';
            }
        }
        $output .= '</ul>';
    }
    $output .= '</div>';

    // Column 4: sub-sub-subcategories (only show if any exist)
    if (!empty($sub_sub_sub_category_lists)) {
        $output .= '<div class="mega-cat-col-4">';
        foreach ($sub_sub_sub_category_lists as $sub_sub_id => $sub_sub_subs) {
            $sub_sub_term = get_term($sub_sub_id, 'product_cat');
            $sub_sub_slug = $sub_sub_term ? $sub_sub_term->slug : '';

            $output .= '<ul class="sub-sub-sub-category-list" data-parent="' . esc_attr($sub_sub_slug) . '">';
            foreach ($sub_sub_subs as $sub_sub_sub) {
                $output .= '<li data-slug="' . esc_attr($sub_sub_sub->slug) . '">'
                         . '<a href="' . get_term_link($sub_sub_sub) . '">' . esc_html($sub_sub_sub->name) . '</a></li>';
            }
            $output .= '</ul>';
        }
        $output .= '</div>';
    }

    $output .= '</div></div>';

    return $output;
}
add_shortcode('custom_product_categories', 'wc_custom_product_category_shortcode');


function wc_custom_product_category_footer_script() {
    echo '
    <script type="text/javascript">
    document.addEventListener("DOMContentLoaded", function() {
        // Loop through each mega-category-list container
        document.querySelectorAll(".mega-category-list").forEach(function(megaCategoryWrapper) {

            megaCategoryWrapper.addEventListener("click", function(e) {
                // Only proceed if the clicked element (or its ancestor) is an <a> with href="#"
                const anchor = e.target.closest("a[href=\'#\']");
                if (!anchor) {
                    return; // We only care about links with href="#"
                }
                e.preventDefault(); // Prevent default anchor behavior

                // Identify which level we clicked by checking the parent <ul> class
                const parentLi = anchor.closest("li");
                const parentUl = anchor.closest("ul");

                // Helper to remove "active" from matching elements
                function removeActiveClasses(selector) {
                    megaCategoryWrapper.querySelectorAll(selector).forEach(function(el) {
                        el.classList.remove("active");
                    });
                }

                // ----- LEVEL 1: custom-parent-categories -----
                if (parentUl.classList.contains("custom-parent-categories")) {
                    // Remove .active from all items at this level
                    parentUl.querySelectorAll("li").forEach(function(item) {
                        item.classList.remove("active");
                    });
                    parentLi.classList.add("active");

                    // Clear deeper levels
                    removeActiveClasses("ul.sub-category-list.active");
                    removeActiveClasses("ul.sub-sub-category-list.active");
                    removeActiveClasses("ul.sub-sub-sub-category-list.active");

                    // Find and show the matching sub-category list
                    const parentSlug = parentLi.getAttribute("data-slug");
                    const subCategoryList = megaCategoryWrapper.querySelector(
                        "ul.sub-category-list[data-parent=\'" + parentSlug + "\']"
                    );
                    if (subCategoryList) {
                        subCategoryList.classList.add("active");
                    }

                    // Set data-depth
                    megaCategoryWrapper.setAttribute("data-depth", "1");
                    return;
                }

                // ----- LEVEL 2: sub-category-list -----
                if (parentUl.classList.contains("sub-category-list")) {
                    // Remove .active from all items at this level
                    parentUl.querySelectorAll("li").forEach(function(item) {
                        item.classList.remove("active");
                    });
                    parentLi.classList.add("active");

                    // Clear deeper levels
                    removeActiveClasses("ul.sub-sub-category-list.active");
                    removeActiveClasses("ul.sub-sub-sub-category-list.active");

                    // Find and show the matching sub-sub-category list
                    const subSlug = parentLi.getAttribute("data-slug");
                    const subSubCategoryList = megaCategoryWrapper.querySelector(
                        "ul.sub-sub-category-list[data-parent=\'" + subSlug + "\']"
                    );
                    if (subSubCategoryList) {
                        subSubCategoryList.classList.add("active");
                    }

                    // Set data-depth
                    megaCategoryWrapper.setAttribute("data-depth", "2");
                    return;
                }

                // ----- LEVEL 3: sub-sub-category-list -----
                if (parentUl.classList.contains("sub-sub-category-list")) {
                    // Remove .active from all items at this level
                    parentUl.querySelectorAll("li").forEach(function(item) {
                        item.classList.remove("active");
                    });
                    parentLi.classList.add("active");

                    // Clear deeper levels
                    removeActiveClasses("ul.sub-sub-sub-category-list.active");

                    // Find and show the matching sub-sub-sub-category list
                    const subSubSlug = parentLi.getAttribute("data-slug");
                    const subSubSubCategoryList = megaCategoryWrapper.querySelector(
                        "ul.sub-sub-sub-category-list[data-parent=\'" + subSubSlug + "\']"
                    );
                    if (subSubSubCategoryList) {
                        subSubSubCategoryList.classList.add("active");
                    }

                    // Set data-depth
                    megaCategoryWrapper.setAttribute("data-depth", "3");
                    return;
                }
            });
        });
    });
    </script>
    ';
}
add_action('wp_footer', 'wc_custom_product_category_footer_script');
				
			
CSS

This can be put anywhere as long as it loads whenever the shorcodes are used. Appearance -> Customise -> Additional CSS is where we’ve been putting extra CSS lately.

Note: Content width is only defined for Desktop and Laptop because it is assumed that the mega menu will be hidden on tablet due to it’s size.

				
					.mega-category-list-wrapper ul {
    list-style-type: none;
    padding: 20px 0;
    width: 100%;
}

.mega-category-list-wrapper ul li {
    padding: 7px 15px 7px 3px;
    margin: 3px 0 !important;
    width: fit-content;
}

.mega-category-list-wrapper ul li a {
    color: white;
}
.mega-category-list-wrapper ul li a:hover {
    opacity: 0.7;
}

.mega-category-list {
    padding: 0;
}

.mega-category-list-wrapper {
    display: flex;
    gap: 0;
    width: min(1300px, 100% - 80px);
    margin: 0 auto;
}

@media only screen and (max-width: 1380px) {
    .mega-category-list-wrapper {
        width: max(100% - 80px, 984px);
    }
}


.mega-cat-col-1,
.mega-cat-col-2,
.mega-cat-col-3,
.mega-cat-col-4 {
    width: 25%;
}

.mega-cat-col-2,
.mega-cat-col-3,
.mega-cat-col-4 {
    padding-left: 40px;
}

.mega-category-list[data-depth="0"] {
    background: #00114F;
}

.mega-category-list[data-depth="1"] {
    background: linear-gradient(90deg, #00114F 0% 50%, #172660 50% 100%);
}

.mega-category-list[data-depth="1"] .mega-category-list-wrapper {
    background: linear-gradient(90deg, #00114F 0% 25%, #172660 25% 100%);
}

.mega-category-list[data-depth="2"] {
    background: linear-gradient(90deg, #00114F 0% 50%, #323D67 50% 100%);
}

.mega-category-list[data-depth="2"] .mega-category-list-wrapper {
    background: linear-gradient(90deg, #00114F 0% 25%, #172660 25% 50%, #323D67 50% 100%);
}

.mega-category-list[data-depth="3"] {
    background: linear-gradient(90deg, #00114F 0% 50%, #485071 50% 100%);
}

.mega-category-list[data-depth="3"] .mega-category-list-wrapper {
    background: linear-gradient(90deg, #00114F 0% 25%, #172660 25% 50%, #323D67 50% 75%, #485071 75% 100%);
}

.mega-category-list-wrapper ul li.active {
    background: #B70007;
}

.sub-category-list,
.sub-sub-category-list,
.sub-sub-sub-category-list {
    display: none;
}

.sub-category-list.active,
.sub-sub-category-list.active,
.sub-sub-sub-category-list.active {
    display: block;
}