Drupal 8 Twig: add custom CSS classes to menus (based on menu name)

Twig templates of Drupal 8 makes our life much easier when we want to customize the HTML output. But when the goal is to change a Drupal 8 menu we have to use the menu.html.twig template which is not the most friendly one and it’s customization can be tricky because of several reasons. So I wanna show you how I did it.

Goal

Change the HTML output from this…

HTML output sample of a Drupal 8 menu without using any template / base theme

…to this…

Drupal 8 menu HTML has BEM style CSS classes when we use our own menu.html.twig template

…so we can keep our CSS specificity low and our CSS component easy to write and maintain.

Let’s start it!

When you turn on Drupal’s Twig debug mode you can see template name suggestions in the generated HTML of your site like this:

This means that if you want to change the HTML of your main menu you can save a copy of menu.html.twig into the templates folder of your theme naming it menu--main.html.twig and then customize it.

But if you have to deal with more menus, creating a custom template for all of them can be daunting: creating / maintaining more templates means more time and more work. What if we could use only one template and generate custom CSS classes for every menu automatically?

menu_name to the rescue

You can read at the beginning of the template file that available variables include menu_name which is “the machine name of the menu”.

“Great! Although I do not understand completely the content of this long template it is easy to locate the ul and li elements and add menu_name as a class to them. I just have to write the variable between double curly braces!” — I thought like this and did it immediately. And… nothing happened! Boo! What the heck?! What did I wrong?!

I started to look for the answer and I learned from this drupal.org issue (and from its parent issue) that “menu_name needs to get past into the menu_links macro because it is only globally available like items”.

“OK. This long complicated code in the template is — mostly — a macro. Following the information in the template comments and in the issues let me know that macros are similar to functions in PHP. And what to do now?” — I wondered.

Step 1: add CSS class to the top level “ul” element

This is the aforementioned code from the original menu.html.twig template.

menu.html.twig template of Drupal 8 as it can be found in Stable theme (introducing comments removed)

To have the desired class on the top level ul element we have to do three things. First we have to pass menu_name as an argument to the macro at the top level.

Then we create the class name from menu_name in a new variable.

At last we add the new CSS class to the top level ul element.

Step 2: add CSS class to “ul” elements below the top level

To have a CSS class on all sub level ul elements we follow the second and third step from above. But we also have to remove the top level class because it leaks down.

Step 3: add CSS class to menu items (“li” elements)

Nothing new is here. We know what to do. :)

(Well. Almost. Small difference is that instead of creating only one class we add three more based on the status of the menu item. Same thing can be found in Classy theme.)

Step 4: add CSS class to menu links (“a” elements)

This is a little bit trickier because — as you can see — there are no anchor tags in the template. But there is the link function instead. We have to add the class to this.

We also have to remove the menu item classes because they are leaking.

And we have to pass menu_name as an argument to the macro again.

We’re done

Here is our full custom template.

Something else yet?

Notice that this thing works fine if you have only one instance of a menu. However you may face a problem if you have more instances of the same menu and — obviously — you want to style them differently (eg. main menu top level items in the header and main menu sub level items in the sidebar).

In this second case you may override the styling by adding one more selector from a parent HTML element and increasing CSS specificity. This is not recommended!

Better to create another template with custom name based on the attributions of the Drupal block which contains the menu.

Another thing that if you compare the HTML output for menu items and menu links (at the top of this post) you may notice that the management of states are different. Menu items use BEM modifier classes but menu link has .is-active state class (SMACSS method).

It would be easy to modify the classes of menu items but it would not fit to BEM. Than I should change the class on the menu link. OK. This should be in a follow up update of this post because — to tell the truth — I do not know yet where that class is coming from and how to change it.

(If you have a solution to this or have any other recommendation to make the code above better or the explanation more accurate please let me know and write it in a comment!)