Data attributes on HTML elements can be very handy for targeting certain elements with CSS and/or Javascript.
Let's say, for example, we have a site with a menu whose links have a different background colour depending on what category they belong to, cats or dogs. The HTML might look like this:
<ul class="menu">
<li class="menu-item">
<a href="#">Cats rule</a>
</li>
<li class="menu-item">
<a href="#">Dogs are the best</a>
</li>
</ul>
All "cat" menu items should have a background colour of blue, and all "dog" menu items a background of green.
We want this to happen automatically, without site editors having to do anything extra, and without adding a module. At first I thought I could target the menu items by their text content using CSS attribute selectors, but you can't target text nodes with CSS.
You can target the href attribute, so I do something like this:
ul.menu a[href*="cat"] {
background: blue;
}
ul.menu a[href*="dog"] {
background: green;
}
However, we don't necessarily know if the URLs will match up with the menu item text. So I set about finding a way to add a data-attribute to the menu items that will be set to the link title, so I can then target the data attribute with CSS.
We're going to be editing menu.html.twig, found inside your Drupal core theme (probably Classy or Stable) templates/navigation folder. Don't edit that file—copy it into your theme's "templates" folder first, then edit the copied file.
We're going to be working inside the "for" loop that prints out all the menu items:
{% for item in items %}
{%
set classes = [
'menu-item',
item.is_expanded ? 'menu-item--expanded',
item.is_collapsed ? 'menu-item--collapsed',
item.in_active_trail ? 'menu-item--active-trail',
]
%}
<li{{ item.attributes.addClass(classes) }}>
{{ link(item.title, item.url) }}
{% if item.below %}
{{ menus.menu_links(item.below, attributes, menu_level + 1) }}
{% endif %}
</li>
{% endfor %}
What we want to do is add a data-attribute to the <li>
element with the item.title variable, which is the text of the menu link.
First, we create a new variable which we set to the item title, then we apply the clean_class filter which lowercases it and replaces any spaces with hyphens:
{% set link_title = item.title|clean_class %}
We then add a new attribute, data-title, to the list items, the value of which is set to our link_title variable:
<li{{ item.attributes.addClass(classes).setAttribute('data-title', link_title) }}>
(For more info, see "Using attributes in templates" in the Drupal 8 guide).
The entire piece of code:
{% for item in items %}
{%
set classes = [
'menu-item',
item.is_expanded ? 'menu-item--expanded',
item.is_collapsed ? 'menu-item--collapsed',
item.in_active_trail ? 'menu-item--active-trail',
]
%}
{% set link_title = item.title|clean_class %}
<li{{ item.attributes.addClass(classes).setAttribute('data-title', link_title) }}>
{{ link(item.title, item.url) }}
{% if item.below %}
{{ menus.menu_links(item.below, attributes, menu_level + 1) }}
{% endif %}
</li>
{% endfor %}
After clearing caches, we now see in our menu:
<ul class="menu">
<li class="menu-item" data-title="cats-rule">
<a href="#">Cats rule</a>
</li>
<li class="menu-item" data-title="dogs-are-the-best">
<a href="#">Dogs are the best</a>
</li>
</ul>
and we can write our CSS like this:
ul.menu li[data-title*="cat"] {
background: blue;
}
ul.menu li[data-title*="dog"] {
background: green;
}
Comments
Spent quite a while trying to figure out how to accomplish this this afternoon and was super grateful to have come across this article. Much more straightforward than using preprocess or module hooks to add a simple data attribute. Thanks!