In our menus recipe, we show how to build several types of menus with Kirby. However, those examples work with main pages and their children and have their limits when it comes to building more flexible menus where the depth of a page is not important or where you need multiple menus with selected pages.
In this recipe, we will look how we can build such custom menus where editors can pick the pages they want to include in a menu, or how we can even include external links.
In this recipe we assume that these menus are created in
site.yml, so that we use the
$site object when rendering them in the template. You can of course also use these menus in a page and adjust the template code accordingly.
The easiest way to create a custom menu from existing pages is the use of a pages field:
This setup can be used for a main menu, or for different single-level menus in the footer or in a sidebar.
In the template:
The above works well if the required menus are known in advance, so that we can provide a field per menu.
In some cases, for example for a footer with multiple menus, we need some more flexibility. In such a case, where the number of menus can vary, a structure field is more useful.
In the template, we can use it like this to render each menu with its headline:
We loop through the structure items and create a new
nav tag for each menu. Inside the
nav tag, we render the title for each menu and the navigation items as a list.
What if we want mix external links with Kirby pages in our menu? We can also achieve this with a structure field.
Here, we show the
pageLink field to select a page if
isExternal is false, and the
externalLink field if
isExternal is set to true. For both types of links the editor can also set a custom title.
In the template:
In our template, we loop through all items and check if the provided link is a page or an URL. We render the title from the structure item if it is given, otherwise we fall back to the page title for a page link or to the shortened URL for external links.
In our blueprint, we create a structure field with the following setup:
The structure field has a pages field for the first level of menu items, and a second conditional pages field for the submenu items that is only shown to editors if they set the
hasSubmenu toggle field to
true. The toggle field is not absolutely necessary but helps to keep the form concise.
mainMenu pages field is limited to
max: 1 pages, whereas the
subMenu field can have multiple subpages.
We can now render this menu in the template like this:
First we check if our structure field contains items with
$menu->isNotEmpty(), because we don't want to render empty
We then loop through the structure items, and for each first level item we check if the stored value is actually a page.
For the second level menu items, we also check if the pages collection (
$item->subMenu()->toPages()) is empty, and only render the submenu if there are items.
If you need more menu levels, you can extend this example and nest another structure field inside the first level structure.
We can also combine this example with external links like in the example before.
Since our fields are repetive, we split them into two parts.
The first part is our nested menu structure field:
Inside this field, we reuse a group of fields we have defined in
/site/blueprints/fields/menufields.yml, because we need these 4 fields in both structure levels:
This example allows us to have conditional external links both on the first and on the second levels of the menu. Editors can also add an optional link title.
Let's see how we can handle this field definition in our template. To keep our code dry, we use a snippet inside the template:
Inside the snippet, we recursively call the snippet for the submenu items:
As always, this is just a basic collection of ideas that you can extend for your use cases. In the structure field examples, you could, for example, include additional fields for link attributes, like
target: _blank for those clients who absolutely want to open links in new tabs.
Instead of using conditional fields for page links vs. external links, you could also use the link field plugin.