Applying BEM inspired classes to your Drupal 8 theme

Applying BEM inspired classes to your Drupal 8 theme

So, you want to apply BEM inspired classes to your next Drupal 8 project? Understanding the theory and syntax is step one. Step two is making it a reality with your Twig templates.

BEM: A Crash Course

BEM code example

<form class="site-search  site-search--full">
    <input type="text" class="site-search__field">
    <input type="Submit" value ="Search" class="site-search__button">
</form>

Code example from MindBEMding – getting your head ’round BEM syntax

  • B (for block) is the class applied to the outermost element. In this example, that’s ‘site-search’.
  • E (for element) is signified by two underscores ‘__’ that tells us which Block the element belongs to. In this example, that’s ‘site-search__button’.
  • M (for modifier) is an additional class that can apply style overrides to a Block or Element. In this example, that’s ‘site-search--full’.

BEM 101: If you are unfamiliar with BEM you might want to review the Key Concepts from the BEM documentation for additional insight. I also recommend MindBEMding – getting your head ’round BEM syntax which is a great introductory post.

The Node Template (node.twig.html)

When reviewing a design and deciding how to componentize the design elements, I almost always consider a Drupal node to be the B (or Block) in BEM. With that, the node template is a good place to start.

While learning the in’s and out’s of Drupal 8’s new theme layer, I started by investigating Drupal core’s base themes: stable and classy (Learn more about Stable and Classy base themes). My personal preference is to build off of the stable theme. Simply because I like to have control over the classes that are used in my theme. Having said that, I frequently refer to the classy theme to see what was possible -- sometimes I will take the classy template as a starting point and then start to remove the extra classes and markup I do not need.

With that in mind, let’s look at the node classes Drupal’s classy theme starts us off with:

<article class=”node node--type-article node--view-mode-full”>
  // Node content here
</article>

I can’t say these classes don’t follow the BEM methodology because they actually do. If we’ve defined our Block as .node, our M (or modifier) is getting properly applied for the node type with .node--type-article, and view mode with .node--view-mode-full.

Categorizing our Block as .node works but only if all nodes are to be styled the same. When following the BEM methodology, we should be able to style .node and .node--view-mode-full on it’s own. As it stands right now, if an Article content type and a Testimonials content type needed different styles for the full view mode, we would have to chain these classes together to prevent style conflicts:

.node--type-article.node--view-mode-full {
  // Article styles here
}

.node--type-testimonial.node--view-mode-full {
  // Testimonial styles here
}

If my project has content types that require unique styling, which is most often the case, I would rather my classes to be:

<article class=”article article--layout-full node”>
  // Node content here
</article>

<article class=”testimonial testimonial--layout-full node”>
  // Node content here
</article>

Note: The node class is required if you want Drupal’s inline editing to work.

With this in place we can style each individual element without fear of impacting other content types.

.article {
  // Global article styles here
}
.article--layout-full {
  // Modifier styles for the full page
}
.article--layout-teaser {
  // Modifier styles for the teaser display
}

.testimonial {
  // Global article styles here
}
.testimonial--layout-full {
  // Modifier styles for the full page
}
.testimonial--layout-teaser {
  // Modifier styles for the teaser display
}

Node.twig.html

To achieve this, here is an example of the node template we should have in place. At the top of the Twig template we are defining an array of classes with the variables that the template makes available to us. Most notably, that is node.bundle which will return the machine name for your content type and view_mode will return the layout option. We are also applying Twig’s clean_class filter. This will convert multi-word machine names like my_content_type to my-content-type.

node.twig.html

{# Create classes array. The 'node' class is required for contextual edit links. #}
{% set classes = [
  node.bundle|clean_class,
  view_mode ? node.bundle|clean_class ~ '--layout-' ~ view_mode|clean_class
  'node'
] %}
{% set title_classes = [
  node.bundle|clean_class ~ '__title'
] %}

<article{{ attributes.addClass(classes) }}>

  {% if title_prefix or title_suffix or display_submitted or page is empty and label %}
    <header>
      {{ title_prefix }}
      {% if not page and label %}
        <h2{{ title_attributes.addClass(title_classes) }}>
          <a href="{{ url }}" rel="bookmark">{{ label }}</a>
        </h2>
      {% endif %}
      {{ title_suffix }}

      {% if display_submitted %}
        <div>
          {{ author_picture }}
          {% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %}
          {{ metadata }}
        </div>
      {% endif %}

    </header>
  {% endif %}

  {{ content }}

</article>

Node Results

Now that we have our node template in place, we can expect markup like this:

<article class=”article article--layout-teaser node”>

    <header>
        <h2><a href="/article/article-title">Article title</a></h2>
        <div class="submitted">Submitted by Jane Doe on March 15, 2016</div>
    </header>

    <div>
        <img src=”/images/my-post-image.jpg” />
    </div>

    <div>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In nec libero quam. Donec convallis tellus et lorem pulvinar, vitae ornare ligula sodales. Nam vestibulum eros eget nibh ultrices, non sagittis mauris blandit...</p>
        <button><a href="/article/article-title">Read more</a></button>
    </div>

</article>

The Field Template (field.twig.html)

So we’ve touched on the B and M: Block and Modifier. What about the E (or Element)? These are the elements that would be nested within your Blocks. When relating this back to a Drupal node, this will most frequently be a field attached to a content type.

Like with the node, let’s look at the field classes Drupal’s classy theme:

<div class=”field field--name-field-image field--type-image field__item”>
  // Field content here
</div>

To style this field, we would have to chain together classes to avoid conflicts between content types:

.article .field--name-field-image {
  // Article image styles here
}

.testimonial .field--name-field-image {
  // Testimonial image styles here
}

To build off of what we have started at the node template level, we would like our markup to be:

<article class=”article article--layout-full node”>

  <h1 class=”article__title”>
    // Field content here
  </h1>

  <div class=”article__image”>
    // Field content here
  </div>

  <div class=”article__body”>
    // Field content here
  </div>

</article>

This will let us style each element individually without nesting.

.article {
  // Global article styles here
}

.article__title {
  // Field styles here
}

.article__image {
  // Field styles here
}

Field.twig.html

To achieve this, here is an example of the field template we should have in place. Like with the node template, we are defining an array of classes with the variables that the template makes available to us.

By default, all Drupal field machine names start with field_. This label doesn’t really bring any value to the class name so I am removing it with the Twig replace filter.

field.twig.html

{% extends "@stable/field/field.html.twig" %}

{# Create classes array #}
{% set classes = [
  bundle ~ '__' ~ field_name|replace({'field_' : ''})|clean_class
] %}

{% set attributes = attributes.addClass(classes) %}

Note: Becasue we are not altering the markup of the field template, here we can just apply our classes while we @extend the the stable's field.twig.html. (Thanks Wim! See comment)

THEME.theme

But we need a little help before we can make this actually work. field.html.twig does not have a bundle variable available to the template. So we need to add the following code to THEME.theme (where THEME is your theme name) to make that variable return a value.

THEME.theme

function THEME_preprocess_field(&$variables, $hook) {
  // Make additional variables available to the template.
  $variables['bundle'] = $variables['element']['#bundle'];
}

Node & Field Results

With both our node and field templates in place, we can expect markup like this:

<article class=”article article--layout-teaser node”>

    <header>
        <h2 class=”article__title”><a href="/article/article-title">Article title</a></h2>
        <div class="submitted">Submitted by Jane Doe on March 15, 2016</div>
    </header>

    <div class=”article__image”>
        <img src=”/images/my-post-image.jpg” />
    </div>

    <div class=”article__body”>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In nec libero quam. Donec convallis tellus et lorem pulvinar, vitae ornare ligula sodales. Nam vestibulum eros eget nibh ultrices, non sagittis mauris blandit...</p>
    </div>

</article>

See it in action

Download the latest development release of the Basic theme for Drupal 8 if you want something to get started with -- and let us know what you think.