Drupal & Template Patterns: One Man’s Quest for Markup Nirvana

The Current State of Drupal Markup

I’ve been using Drupal for over 10 years and I’ve come to appreciate its flexibility in handling complex projects. But for a front-end developer this flexibility sometimes comes at a price, and like many developers out there, I hate — probably more than most — the sheer number of divs it generates for seemingly no good reason.

As a stickler for good HTML markup, dealing with Drupal is particularly challenging for me and has contributed to many love/hate moments during Drupal projects (in my frustration, I’ve even resorted to creating a module with coworkers to help us clean up the default markup in blocks and panels). The good news is that Drupal 8 is a massive improvement in many ways. The bad news is that for markup purists like me, additional work is still required to get the exact output we want.

One of my particular areas of frustration is fields. Like everything else in Drupal, it feels like everything is in a div wrapped in more divs. Again, while I appreciate how far Drupal 8 has come, it still hasn’t brought us to the point where we can dynamically assign markup to fields.

A few different approaches for controlling the markup of fields already exist — custom template files, contrib modules like display suite — but I’ve been searching for a simpler way. I want one that lets me avoid having to create a bunch of identical field template files or having to enter the same parameters in multiple fields in Display Suite a million times (plus, there’s an argument to be made that contrib modules that enhance markup should be avoided in Drupal 8).

Why Should You Care?

Other than taking pride in doing the best job possible, there are a few benefits to optimizing the HTML markup. In my experience, clean HTML leads to clean CSS. For example, if you have a simple text field that you would like to output as a sub-title within a node, outputting that field as an h3 will make things a lot simpler than styling a standard div, given that you probably have already globally styled your headings. I find this particularly critical on large projects. With the potential for many front-end developers to have their fingers in the pie, reducing markup complexity also reduces the chances for confusing CSS rules to creep up.

It’s also not necessarily just about reducing markup but making it semantic, which is to say making it meaningful. We spend a lot of time making code look a certain way to visually communicate something, and frankly the tone of our markup should reflect that, especially when the other option is using vague and monotonous divs everywhere.

Plus, Google says so.

Finding Our Way to Markup Nirvana

Given my eternal quest for optimal markup in Drupal, it was only a matter of time before I came up with a means to speed up the process of improving a field’s output. On a recent project, my coworkers and I created a series of “template patterns” — essentially template files that could be reused for multiple fields. Several were project specific, but we ended up creating a number of patterns that are globally useful:

  1. Reset: removes any wrappers
field--pattern--reset.html.twig

2. List: converts multiple field items to an unnumbered/bulleted list

field--pattern--list.html.twig

3. Headings: converts simple text fields to use h1 to h6 headings

field--pattern--h2.html.twig

4. Paragraph: converts simple text fields to paragraphs

field--pattern--p.html.twig

5. Wrapper only: outputs field wrapper only, without the wrapper on individual field items

field--pattern--wrapper--only.html.twig

We won’t discuss all of these today, but we’ll take a closer look at a couple of examples. Let’s start with the first one, the Reset template pattern, and then we’ll look at the slightly more nuanced List template pattern.

Creating a “Reset” Pattern

Sometimes in life, simplicity is best. To start us off, let’s go for the most zen thing and output the field without any wrappers.

Step 1: Create function to assign template suggestion to fields

First let’s create a function that will assign the suggestion to our field, in our theme’s .theme file:

function field_assign_suggestion($hook, $template, &$suggestions) {
foreach ($suggestions as $suggestion) {
if ($hook == $suggestion) {
$suggestions[] = ‘field__pattern__’ . $template;
}
}
}

Step 2: Create the field template twig file

Next let’s create the twig template file. Let’s call this file field — pattern — reset which would give us

field--pattern--reset.html.twig 

in Drupal 8. Here’s the content of the file:

{% for item in items %}
  {{ item.content }}
{% endfor %}

Step 3: Assign template suggestion to specific field

The last step is to use Drupal’s theme_suggestions_HOOK_alter to assign the “reset” pattern to a specific image field. In other words, we need to tell Drupal to use our new template for a specific field. In this case, to the field_image. (Just remember to replace THEMENAME with the name of your theme.)

function THEMENAME_theme_suggestions_field_alter(array &$suggestions, array $variables) {
// assign patterns suggestions to specific fields.
field_assign_suggestion(‘field__field_image’, ‘reset’, $suggestions);
}

The function has three parameters: a template suggestion for the field (more on that in a moment), the desired “pattern”, and the suggestion array.

Once you’ve cleared your site cache, your code should have gone from this:

<div class="field field--name-field-image field--type--image field --label-hidden field__item">
<img src="/path/to/image.jpg" width="480" height="319"
alt="Some text describing the image" />
</div>

To this:

<img src="/path/to/image.jpg" width="480" height="319" alt="Some text describing the image" />

Congrats! You’re on your way to becoming a markup ninja! And from now on, if you need to use the “reset” pattern, you only need to do step three since you’ve already added the function and the Twig template.

Tips for Finding Template Suggestions

If you’re not familiar with Drupal 8 or Drupal theming in general, figuring out which template suggestions currently exist might be a bit tricky. To get this info, you’ll have to set twig debugging to “true” in your services.yml file (see https://www.drupal.org/node/2358785). In our example, the image field had the following suggested twig override file names:

field--node--field-image--article.html.twig
field--node--field-image.html.twig
field--node--article.html.twig
field--field-image.html.twig
field--image.html.twig
field.html.twig

Inside of our field_assign_suggestion function when we assign values to the $suggestions array, replace the hyphens with underscores and remove the file name extension:

field--node--field-image--article.html.twig 

would become

field__node__field_image__article.

Another way is to use the devel module to do a dpm() to output the list of suggestions:

function THEMENAME_theme_suggestions_field_alter(array &$suggestions, array $variables) {
dpm($suggestions)
}

Outputting a field as an HTML list

Now that you’ve mastered “reset” let’s take a look at another example, one that would probably be even more useful: converting a multi-item field to a “list”. Now this is what we get out of the box with Drupal 8:

<div class="field field--name-field-tags field--type-entity-reference field--label-above quickedit-field">
<div class="field__label">Tags</div>
<div class="field__items">
<div class="field__item"><a href="/taxonomy/term/1">Testing</a></div>
<div class="field__item"><a href="/taxonomy/term/2">Cats</a></div>
<div class="field__item"><a href="/taxonomy/term/3">Dogs</a></div>
<div class="field__item"><a href="/taxonomy/term/4">Tigers</a></div>
</div>
</div>

But we would like this:

<div class="field field--name-field-tags field--type-entity-reference field--label-above">
<h3 class="field__label">Tags</h3>
<ul class="field__items">
<li class="field__item"><a href="/taxonomy/term/1">Testing</a></li>
<li class="field__item"><a href="/taxonomy/term/2">Cats</a></li>
<li class="field__item"><a href="/taxonomy/term/3">Dogs</a></li>
<li class="field__item"><a href="/taxonomy/term/4">Tigers</a></li>
</ul>
</div>

It’s not that there’s significantly less markup in this example. What matters is the markup is meaningful and semantic. Let’s get to it!

Step 1: Create the field template twig file

Now that our function exists, this should be relatively simple. First let’s create a twig template file called

field--pattern--list.html.twig 

which contains the following code:

{%
set classes = [
'field',
'field--name-' ~ field_name|clean_class,
'field--type-' ~ field_type|clean_class,
'field--label-' ~ label_display,
]
%}
{%
set title_classes = [
'field__label',
label_display == 'visually_hidden' ? 'visually-hidden',
]
%}
{% if label_hidden %}
{% if multiple %}
<ul{{ attributes.addClass(classes, 'field__items') }}>
{% for item in items %}
<li{{ item.attributes.addClass('field__item') }}>{{ item.content }}</li>
{% endfor %}
</ul>
{% else %}
{% for item in items %}
<div{{ attributes.addClass(classes, 'field__item') }}>{{ item.content }}</div>
{% endfor %}
{% endif %}
{% else %}
<div{{ attributes.addClass(classes) }}>
<h3{{ title_attributes.addClass(title_classes) }}>{{ label }}</h3>
{% if multiple %}
<ul class="field__items">
{% endif %}
{% for item in items %}
<li{{ item.attributes.addClass('field__item') }}>{{ item.content }}</li>
{% endfor %}
{% if multiple %}
</ul>
{% endif %}
</div>
{% endif %}

Notice that not only did we output each individual item as a list item, we also changed the label to a heading 3 (feel free to change to a heading that is more applicable to your site).

Step 2: Assign template suggestion to specific field

Now that we have a twig template file we need to tell Drupal to use this with one of our fields. In this example, I have a “Tags” field and I know that one of its suggestions is field__node__field_tags., so I just need to add the following line to our suggestion hook from above:

field_assign_suggestion(‘field__node__field_tags’, ‘list’, $suggestions);

All that’s left is to clear caches and we’re done!

In Closing

Clean markup is often overlooked, especially in Drupal land. But taking a few extra steps to understand and implement regular markup patterns will go a long way towards improving your site’s overall semantics, which enhance accessibility and makes Google and site maintainers happy.

Published by: Rene Hache

Are you a markup perfectionist, stickler, enforcer, nitpicker? Do you have tips for us? We’d love to hear from you! Leave a comment and let us know what works for you…

Or better yet, come work for us! We’re looking for a Senior Drupal Developer — apply here.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.