Skip to content

Menus ​

Alchemy uses a dedicated menu system that is separate from the page tree. Menus are managed through nodes — tree-structured entries that can link to Alchemy pages, external URLs, or act as grouping labels.

This separation gives editors full control over navigation structure without being constrained by the page hierarchy. A page can appear in multiple menus, or not appear in any menu at all.

Defining Menus ​

Menu types are defined in the config/alchemy/menus.yml file. The installer creates this file with two default menus.

yaml
# config/alchemy/menus.yml
- main_menu
- footer_menu

Each entry is a menu type name. Alchemy creates a root node for each menu type per language. Editors then build the menu tree by adding child nodes in the admin interface.

Generating Menu Partials ​

Alchemy provides a generator that creates view partials for all defined menus.

bash
bin/rails g alchemy:menus

For each menu type, the generator creates two partials:

FilePurpose
app/views/alchemy/menus/<menu_type>/_wrapper.html.erbThe outer container (e.g. <ul>)
app/views/alchemy/menus/<menu_type>/_node.html.erbA single menu item (e.g. <li>)

Rendering Menus ​

Use the render_menu helper in your layout to render a menu.

erb
<%# app/views/layouts/application.html.erb %>
<nav>
  <%= render_menu "main_menu" %>
</nav>

<footer>
  <%= render_menu "footer_menu" %>
</footer>

The helper finds the root node for the given menu type in the current language and renders the wrapper partial. You can pass custom options that are available in your partials.

erb
<%= render_menu "main_menu", class: "navbar" %>

Customizing Menu Partials ​

The Wrapper Partial ​

The wrapper partial receives the root node as menu and any options you passed to render_menu.

erb
<%# app/views/alchemy/menus/main_menu/_wrapper.html.erb %>
<% cache [menu, @page, Alchemy::Current.preview_page?] do %>
  <ul class="nav">
    <%= render partial: menu.to_partial_path,
      collection: menu.children.includes(:page, :children),
      locals: { options: options },
      as: 'node' %>
  </ul>
<% end %>

The Node Partial ​

The node partial receives each node and renders it as a menu item. Child nodes are rendered recursively.

erb
<%# app/views/alchemy/menus/main_menu/_node.html.erb %>
<% cache [node, @page, Alchemy::Current.preview_page?] do %>
  <%= content_tag :li, class: ['nav-item', node.children.any? ? 'dropdown' : nil].compact do %>
    <%= link_to_if node.url,
      node.name,
      Alchemy::Current.preview_page? ? 'javascript: void(0)' : node.url,
      class: ['nav-link', current_page?(node.url) ? 'active' : nil].compact,
      title: node.title,
      target: node.external? ? '_blank' : nil,
      rel: node.nofollow? ? 'nofollow' : nil %>
    <% if node.children.any? %>
      <ul class="dropdown-menu">
        <%= render node.children.includes(:page, :children), as: 'node' %>
      </ul>
    <% end %>
  <% end %>
<% end %>

Customize these partials to match your site's markup and CSS framework.

Node Properties ​

Each node has the following properties that editors can set in the admin interface.

Page ​

An optional Alchemy page. When set, the node's name and URL are taken from the page automatically.

Name ​

A custom label. Required when no page is linked. When a page is linked, the page name is used unless a custom name is provided.

Title ​

An optional title attribute for the link tag.

URL ​

An optional external URL. When a page is linked, the page's URL is used instead.

External ​

Marks the link as external. When true, the link opens in a new tab (target="_blank").

Nofollow ​

When true, adds rel="nofollow" to the link.

Translating Menu Names ​

Menu type names are translated through I18n. These names appear as root node labels in the admin interface.

yaml
# config/locales/alchemy.de.yml
de:
  alchemy:
    menu_names:
      main_menu: Hauptnavigation
      footer_menu: Fusszeile

TIP

See the I18n guide for all available translation scopes.

BSD-3 Licensed · Hosting sponsored by netlify