Building a website with a user-friendly and scalable navigation system is crucial. This blog post dives into the technical aspects of our custom mega menu solution. We’ll explore how we leverage WordPress’s classic menus and integrate them with custom Gutenberg blocks to create a dynamic and manageable navigation experience.
Introduction
When it comes to building a scalable and modular CMS for our clients, our go-to recommendation often involves implementing a WordPress solution accompanied by custom Gutenberg blocks. This way, we develop a fast & secure CMS that our clients can easily manage and scale according to their needs.
WordPress itself comes with some built-in “core” blocks which are flexible and allow users to build simple layouts easily. Often these “core” blocks are not enough for building more advanced layouts & designs, therefore we need to extend Gutenberg editor’s functionality, adding custom blocks tailored to our client needs. This not only enhances their editing experience but also contributes to an elevated and streamlined content creation process.
We opt for a “Hybrid Theme” approach, where we leverage Gutenberg features, empowering our clients to effortlessly create new pages using the custom blocks and modules we build specifically for them, while ensuring alignment with brand guidelines, design system, and overall visual coherence.
Custom Mega Menus functionality, based on Gutenberg editor.
While managing page content is entirely in the client’s hands, the website’s header and footer are strategically implemented through code. This intentional separation enables clients to concentrate solely on page editing, ensuring a user-friendly experience while retaining full control over their content.
Implementing the header and footer through code doesn’t limit client control over their content.
For the website’s navigation, we are using the classic WordPress menus extended with custom functionality for mega menus that allow clients to populate mega menus content using Gutenberg blocks. Through that workflow, clients can easily populate mega menus and attach them to menu items.
How we developed custom mega menus functionality using Gutenberg blocks
In a nutshell, the mega menu logic is as follows: clients populate the mega menus on a custom post type created specifically for this purpose. Within the ‘mega menu’ post type they can use custom Gutenberg blocks that we develop for building up mega menu content. The populated mega menus can be then linked to a top-level menu item through a custom field. Finally, the custom WordPress Nav Menu Walker comes into play, retrieving the mega menu content and displaying it as a child element on that menu item.
Let’s take it step by step:
Step 1: Create ‘Mega Menu’ custom post type
This is a custom post type that we use to populate the mega menus. It’s not public, which means it doesn’t have a single or archive page and we can only use custom blocks created specifically for building the mega menus layout.
Step 2: Create custom Gutenberg blocks, to be used for building the mega menus layouts
Let’s take a closer look at some of the custom blocks that we developed specifically for building mega menu layouts, and explore snippets of code from some essential files that we use for registering these Gutenberg blocks.
Mega Menu Columns block
This block is responsible for adding the columns on the mega menus; it only accepts the ‘mega menu column’ as the inner block. By default, it has an equal-width, 3-column layout, but is flexible enough to support multiple layout variations. Of course, these layouts can be updated depending on individual project requirements.
This block gives us the ability to add link cards on our mega menus, it outputs a card that will be linked to a destination specified by the user. It supports attributes like title, description, background image, and provides customization options for both its default and hover state colors.
Some other blocks that we use inside the mega menus editor include
Navigation List: Outputs a list which accepts navigation links as inner block
Navigation Link: This is the block that we use for adding links to our navigation list
Step 3: Link mega menus with menu items
Now let’s take a closer look at how we can add a custom field to our menu items, where it will be used to connect the menu items with the mega menus.
<?php
/**
* Add custom fields to the menu item in the menu editor.
*
* @param int $item_id The ID of the menu item.
* @param object $item The menu item object.
* @param int $depth The depth of the menu item.
* @param array $args An array of arguments.
*/
function bb_add_menu_items_custom_fields($item_id, $item, $depth, $args)
{
$selectedMegaMenu = get_post_meta($item_id, '_menu-item-mega-menu-select', true);
?>
<!-- Mega menu select -->
<p class="menu-item__custom-field field--mega-menu-select description description-wide">
<label>
<?php _e("Mega Menu Select (Main menu only)", 'bb-agency'); ?> <br>
<select class="widefat" name="menu-item-mega-menu-select[<?php echo $item_id; ?>]" id="menu-item-mega-menu-select-<?php echo $item_id; ?>">
<option value="" <?php selected($selectedMegaMenu, '') ?>> None </option>
<?php
$args = array(
'post_type' => 'mega-menu',
'post_status' => 'publish',
'posts_per_page' => -1,
);
$the_query = new WP_Query($args); ?>
<?php if ($the_query->have_posts()) : ?>
<?php while ($the_query->have_posts()) : $the_query->the_post(); ?>
<option value="<?php echo get_the_ID() ?>" <?php selected($selectedMegaMenu, get_the_ID()) ?>>
<?php echo get_the_title() ?>
</option>
<?php endwhile; ?>
<?php wp_reset_postdata(); ?>
<?php endif; ?>
</select>
</label>
</p>
<?php
}
add_action('wp_nav_menu_item_custom_fields', 'bb_add_menu_items_custom_fields', 10, 4);
/**
* Save custom fields for menu items when a menu is updated.
*
* @param int $menu_id The ID of the updated menu.
* @param int $menu_item_db_id The ID of the menu item being updated.
* @param array $args An array of arguments.
*/
function bb_update_menu_items_custom_fields($menu_id, $menu_item_db_id, $args)
{
$fields = array('mega-menu-select');
foreach ($fields as $key) {
$value = (isset($_POST['menu-item-' . $key][$menu_item_db_id])) ? $_POST['menu-item-' . $key][$menu_item_db_id] : '';
update_post_meta($menu_item_db_id, '_menu-item-' . $key, $value);
}
}
add_action('wp_update_nav_menu_item', 'bb_update_menu_items_custom_fields', 10, 3);
Step 4: Display mega menu content using custom WordPress Nav Menu Walker
In this step, we will explore the custom WordPress Nav Menu Walker class we’ve developed and how we utilize it to display our mega menus within the main menu.
Custom WordPress_Nav_Menu class
<?php
class BB_Agency_Walker extends Walker_Nav_Menu
{
/**
* Handle the starting of a navigation menu item.
*
* @param string &$output The menu item's HTML output.
* @param WP_Post $item The current menu item.
* @param int $depth Depth of the current menu item.
* @param stdClass $args An object containing menu arguments.
* @param int $current_object_id The ID of the current object.
*/
function start_el(&$output, $item, $depth = 0, $args = null, $current_object_id = 0)
{
$object = $item->object;
$type = $item->type;
$title = $item->title;
$permalink = $item->url;
$id = $item->ID;
$target = $item->target;
$selectedMegaMenu = get_post_meta($id, '_menu-item-mega-menu-select', true);
// Setup menu item classes
$classes = apply_filters('nav_menu_css_class', array_filter($item->classes), $item, $args, $depth);
$titleIcon = '';
if ($selectedMegaMenu && $depth === 0) {
$classes[] = 'menu-item-has-mega-menu';
$titleIcon = '<i class="icon-chevron-down"></i>';
}
// Setup default elements
$defaultStart = '<li class="' . implode(" ", $classes) . '">';
$defaultTitle = '<a href="' . $permalink . '"' . ($target ? 'target="_blank"' : '') . '>' . $title . $titleIcon . '</a>';
// Default output
$defaultOutput = $defaultTitle;
$output .= $defaultStart;
// Return output
$output .= apply_filters('walker_nav_menu_start_el', $defaultOutput, $item, $depth, $args);
}
/**
* Handle the ending of a navigation menu item.
*
* @param string &$output The menu item's HTML output.
* @param WP_Post $item The current menu item.
* @param int $depth Depth of the current menu item.
* @param stdClass $args An object containing menu arguments.
*/
function end_el(&$output, $item, $depth = 0, $args = null)
{
// Get the selected mega menu for the current item
$selectedMegaMenu = get_post_meta($item->ID, '_menu-item-mega-menu-select', true);
$selectedMegaMenuContent = apply_filters('the_content', get_post_field('post_content', $selectedMegaMenu));
// Get item title
$title = $item->title;
if ($selectedMegaMenu) {
// Check if animation is enabled for the mega menu
$enableAnimation = get_post_meta($selectedMegaMenu, '_enable_animation', true);
$class = 'mega-menu-wrapper';
if (!$enableAnimation) {
$class .= ' no-animation';
}
// Generate the HTML for the mega menu content
$output .= '<div class="' . $class . '">';
$output .= '<div class="mega-menu__content-wrapper">';
$output .= '<div class="container">';
$output .= '<div class="mega-menu__content js-mega-menu__content">';
$output .= '<button class="mega-menu-back-btn"><i class="icon-chevron-left"></i>' . $title . '</button>';
$output .= $selectedMegaMenuContent;
$output .= '</div>';
$output .= '</div>';
$output .= '</div>';
$output .= '</div>';
}
$output .= '</li>'; // close li
}
}
That’s about it when it comes to the functionality and the logic for building the mega menus. Now it’s just a matter of some custom CSS & Javascript code to handle the appearance & functionality of our mega menus.
Wrapping Up
In this blog post we’ve explored our tailored approach to WordPress mega menus, and how we integrated classic WordPress menus with the modern Gutenberg editor to create a streamlined and user-friendly way of managing mega menus. This custom approach showcases the adaptability and limitless capabilities of using the Gutenberg editor to create unique and dynamic website experiences.
BB Agency provides a breathtaking new brand and website for HR solutions provider
Work
BB Team
Crushing Misunderstandings: Communication Tips for Remote Employees
Culture
Manage Cookie Consent
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
Functional
Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes.The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
Functional
Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes.The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.