Creating a Mega Menu using Layout Builder in Drupal 8
Creating a Mega Menu using Layout Builder in Drupal 8
Mike Potter | Principal Engineer, CMS
October 1, 2018
One of the most exciting additions to Drupal 8.6 is the new experimental Layout Builder. Many are focused on Layout Builder replacing Panels, Panelizer, Display Suite, and even Paragraphs. The clean and modular architecture of Layout Builder supports a multitude of different use cases. It can even be used to create a WYSIWYG Mega Menu experience.
Note: Experimental
While Layout Builder was first added as experimental to Drupal 8.5, it has changed significantly since and is now considered more "beta" than "alpha". While still technically experimental and not officially recommended for production sites, the functionality and architecture has stabilized with Drupal 8.6 and it's time to start evaluating it more seriously.
What is a Mega Menu?
For the purposes of this discussion, I'll define a "Mega Menu" as simply a navigation structure where each item in the menu can expand to show a variety of different components beyond a simple list of links.
In the above example example, we see a three column menu item with two submenus, a search form, and a piece of static content (or reference to another node).
Mega Menus present many challenges for a site including accessibility, mobile responsiveness, governance and revision moderation, etc. While I don't advocate the use of mega menus, sometimes they are an unavoidable requirement.
Past Solutions
I've seen many different implementations of Mega Menus over the years.
- Modules such as we_megamenu (D8), tb_megamenu (D7), etc.
- Custom blocks (D8),
- Hard-coded links, node references, and Form API rendered in theme,
- MiniPanels rendered in the theme (D7)
- Integrations with Javascript libraries such as Superfish
- Custom site-specific code
These solutions had many problems and often didn't provide any easy way for site owners to make changes. Often these solutions caused headaches when migrating the site or supporting it over a long life cycle. I've known many teams who simply groan when a client mentions "we want mega menus."
Wouldn't it be nice if there was a consistent way in Drupal 8 to create and manage these menus with a component-based design architecture?
Layout Builder
The Layout Builder module can take control over the rendering of an entity view mode. Normally in Drupal, a view mode is just a list of fields you want to display, and in which order. These simplistic lists of fields are usually passed to a theme template responsible for taking the raw field data and rendering it into the designed page.
With Layout Builder, a view mode consists of multiple "sections" that can contain multiple "blocks." A "Section" references a specific "Layout" (2 column, 3 column, etc). Each field of the entity can be displayed via a new field_block. Thus, a traditional view mode is just a single section with a one-column layout filled with a block for each field to be displayed.
The core Layout Discovery module is used to locate the available "layouts" on the site that can be assigned to a Section. Core comes with one column, two column, and three column (33/33/33 and 25/50/25) layouts. Custom layout modules can be easily created to wrap a twig template for any layout needed within a section.
Blocks for each field can be added to a section, along with any other predefined or custom block on the site. Core also provides "inline blocks" that are instances of custom blocks referenced by the node but not shown in the global block layout admin view.
When an inline block is edited, a new revision of the block is created and a new revision of the node entity is created to reference it, allowing layout changes to be handled with the same workflow as normal content changes.
Section Storage
Layout Builder uses a Section Storage plugin to determine how the list of block uuids referenced in a layout section are stored. The default layout for a content type is stored in the third_party_settings for the specific view mode configuration. If node-specific overrides are enabled for the bundle, the overriding list of blocks in the section are stored within a layout_builder__layout
field added to the node.
While the use of Layout Builder is focused on Nodes (such as Landing Pages), the Layout Builder architecture actually works with any entity type that supports the Section Storage. Specifically, any entity that is "fieldable" is supported.
Fieldable Menu Items
If Layout Builder works with any fieldable entity, how can we make a Menu Item entity fieldable? The answer is the menu_item_extras contrib module. This module allows you to add fields to a menu entity along with form and display view modes. For example, you can add an "icon" image field that will be displayed next to the menu link.
The Menu Item Extras module has been used in Drupal 8 for a while to implement mega menus via additional fields. However, in Drupal 8.6 you don't need to add your own fields, you just need to enable Layout Builder for the default menu item view display mode:
When you allow each menu link to have its layout customized, a layout_builder__layout
field is added to the menu item to store the list of blocks in the sections. When you Add a Link to your menu, a new tab will appear for customizing the layout of the new menu link item:
The Layout tab will show the same Layout Builder UI used to create node landing pages, except now you are selecting the blocks to be shown on the specific menu item. You can select "Add Section" to add a new layout, then "Add Block" to add blocks to that section.
In the example above I have used the optional Menu Blocks module to add submenus of the Drupal Admin menu (Structure and Configuration) to the first two columns (default core menu blocks do not allow the parent to be selected, but the Menu Block contrib module adds that). In third column the Search Form block was added, and below that is an "Inline Block" using the core Basic Block type to add static text to the menu item.
Theming the Menu
The Menu Item Extras module provides twig templates for handling the display of the menu item. Each menu item has a "content" variable that contains the field data of the view mode, just like with any node view mode.
Each theme will need to decide how best to render these menus. Using a subtheme of the Bootstrap theme I created the following menu-levels.html.twig
template to render the example shown at the beginning of this article:
<ul{{ attributes.addClass(['menu', 'menu--' ~ menu_name|clean_class, 'nav', 'navbar-nav']) }}>
{% for item in items %}
{% set item_classes = [
item.is_expanded ? 'expanded',
item.is_expanded and menu_level == 0 ? 'dropdown',
item.in_active_trail ? 'active',
]
%}
<li{{ item.attributes.addClass(item_classes) }}>
<a href="{{ item.url }}" class="dropdown-toggle" data-toggle="dropdown">{{ item.title }} <span class="caret"></span></a>
<div class="dropdown-menu dropdown-fullwidth">
{{ item.content }}
</div>
</li>
{% endfor %}
</ul>
Summary
The combination of Layout Builder and Menu Item Extras provides a nearly WYSIWYG experience for site owners to create complex mega menus from existing block components. While this method still requires a contrib module, the concept of making a menu item entity fieldable is a clean approach that could easily find its way into core someday. Rather than creating yet another architecture and data model for another "mega menu module", this approach simply relies on the same entity, field, and view mode architecture used throughout Drupal 8.
While Layout Builder is still technically "experimental", it is already very functional. I expect to see many sites start to use it in the coming months and other contrib modules to enhance the experience (such as Layout Builder Restrictions) once more developers embrace this exciting new functionality in Drupal core.
My thanks to the entire team of developers who have worked on the Layout Initiative to make Layout Builder a reality and look forward to it being officially stable in the near future.