Grandchild elements in BEM

Battling the ever-present issue of Grandchild elements in the popular CSS naming convention, BEM.

By Luke Whitehouse on

CSS

Article

It's not a stretch to say that BEM is the most sought-after naming convention for CSS, yet despite its popularity it can still be misconstrued by even the most seasoned of developers. It's not perfect, granted, but it's a methodology that in the right hands can see a project's CSS blossom into a garden of delightfully scoped components.

In a recent post, I wrote about how you can utilise BEM to create a responsive timeline. I touched on the idea of "grandchild elements", an area that I've seen a lot of developers struggle with, and I wanted to delve deeper into that subject.

Setting the scene

At a base level, BEM provides us with the following practices for CSS selectors, each correlating to a different letter in its name:

B: Block - A standalone entity that is meaningful on its own.
E: Element - A part of a block that has no standalone meaning and is semantically tied to its block.
M: Modifier - A flag on a block or element. Use them to change appearance or behaviour.

getbem.com

and here's how those correlate to actual classes:

.block {}
.block__element {}
.block--modifier {}

Notice that everything is tied to the 'block'? Well, grandchildren kind of break that rule.

Grandchild elements are the idea that a component may have elements within elements. That's to say, an element may be tied to another element, rather than the block itself.

If we did require that functionality, then we'd need to extend the BEM methodology to allow for this. Or, perhaps there are better resolutions? It's a tough one to answer and there's no one rule fits all.

To demonstrate, here's a post from Assortment's home page:

A typical blog post excerpt on Assortment
A typical blog post excerpt on Assortment

and if I were to mark some of that up in HTML:

<article class="post">
  <div class="post__meta">...</div>
  <h1 class="post__heading">Lorem ipsum dolor sit amet</h1>
  <p class="post__excerpt">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
</article>

Going back to the image you'll notice that on mobile the category label and date are centered at the top of the component, whereas on desktop they're positioned on the top left. To allow for this, I wrap them both in a .post__meta element which handles the positioning thanks to some media query goodness.

What gets interesting is how we should then mark up the contents of .post__meta, that being the category and date. There are a few approaches we can take, each having their own pros and cons. Let's dive in.

Flattening grandchildren

<article class="post">
  <div class="post__meta">
    <div class="post__category">...</div>
    <div class="post__date">...</div>
  </div>
  ...
</article>

Our first option is to flatten our grandchildren. Flattening would mean treating them as though they have no relationship with their parent element, despite the tree representing this. Essentially disregarding the notion of grandchild elements altogether.

Whilst a simpler approach, it does decrease the readability of the component moving forward. In the example above, there would be no indication to another developer coming onto your project that the post__date element is to always be wrapped in post__meta, and so could lead to misuse of the component unless external documentation is available. This, in turn, would increase the time it takes for a developer to consume the component correctly, as they now have to the read documentation beforehand.

In an ideal world, we would want the hierarchy to the component to be understood from the CSS selectors alone, which would not be the case here. That being said, there are occasions where this approach could be preferable.

Pattern libraries

If a component were part of a wider design system where documentation is already required for consumption, then it's probably not a big deal for developers to take the extra few seconds to read a few lines on what should be nested within what.

Hell, it may even be more time efficient to do so.

Multi-use grandchildren

In circumstances where a grandchild has the ability to be in multiple places within the component, depending on the design requirements on that page, it can be better to ignore a potential relationship with its parent.

To give an example, what if there were multiple instances of a post's date? We've already established one instance on the home page, but perhaps there's another variation on the post's individual page, that's styled ever so differently. At that point, your element now has multiple ways to be consumed and there's no easy way of explaining that to a developer through any naming convention. At that point, I'd probably go with creating a whole new block.

Creating new blocks

<article class="post">
  <div class="post__meta">
    <div class="category post__category">...</div>
    <div class="date post__date">...</div>
  </div>
  ...
</article>

As with Flattening, creating a new block doesn't actually achieve any relationship between elements and again dismisses the idea of grandchildren, however, it does provide you with a more robust platform to start from. Whether that's needed or not is the main question.

Many developers I've spoken to go with this option as in their eyes if you're required to have a relationship between elements (grandchildren), then your component is probably too complex. Instead, you should separate your concerns and use external documentation as required to explain any relationships of how a component links with another.

In our example above, any category styling would be part of the category namespace, whereas positioning in the post would be done by post__category. This also has the added benefit of ensuring your component isn't tied to any positioning on the post, which is useful if you need to reuse this somewhere else on your website (as I mentioned above).

However, you're not going to want to create a new block for every single element that could do with a small bit of upfront information to developers that it's nested within another element.

Extending the BEM naming convention

If forgetting about grandchildren doesn’t suffice then the only other option is to go rogue, you little cow[boy/girl] you.

Heres a few strategies to choose from.

Option 1: Continued double underscores

<article class="post">
  <div class="post__meta">
    <div class="post__meta__category">...</div>
    <div class="post__meta__date">...</div>
  </div>
  ...
</article>

One of the more popular examples of grandchild elements I see is extending the element syntax (__element) a step further to include it's children.

This nicely ticks the box of allowing the structure to be described through the HTML and more importantly seems the be the first solution a lot of developers come up with so it potentially has some innate learning built in.

That being said, it’s still an extension to the BEM convention and so has a slight overhead for developers coming onto a project with this in place.

Option 2: Hyphen spaced

<article class="post">
  <div class="post__meta">
    <div class="post__meta-category">...</div>
    <div class="post__meta-date">...</div>
  </div>
  ...
</article>

The hyphenated approach could be considered both an extension of the naming convention and flattening the idea of grandchildren at the same time.

In the BEM specification, it explains that names with multiple words should be written using a hyphen to represent the space. So you could break .post__meta-category down to:

  • post being the block;
  • and meta-category as a double-barreled element.

New developers (who’re only familiar with the traditional BEM syntax) won’t be confused and it still conforms to the specification whilst providing the ability to create a nested structure of elements in our selectors.

Of course, we know the true intentions of meta-category, but that's OK. It'll provide us with all the functionality we're looking for without confusing new developers coming onto the project.

The only real downside I've noticed when using this approach is if you start mixing double-barreled element names with grandchildren and how you distinguish between them both. Though to be honest, I can't remember the last time I had a double-barreled name for an element.

Option 3: A completely new syntax

<article class="post">
  <div class="post__meta">
    <div class="post__meta_category">...</div>
    <div class="post__meta_date">...</div>
  </div>
  ...
</article>

If all else fails, there's no harm in extending the BEM syntax altogether.

In the example above I use a single underscore to describe a grandchild element, however, there's no reason it couldn't be three hyphens or a completely new character altogether. (Just keep in mind that if you use a special character you'll need to escape it in your CSS.)

Again, this approach would introduce an initial learning curve for your developers, but going back to the idea of a project which requires external documentation from the get-go, you may not mind a little overhead.

What should you do?

That's the real question, isn't it? I've laid out a few options, but which should you choose? The truth is, I don't know. Like everything in web development, it completely depends on your project and use case.

What I will say is this. Try to limit the times you're extending BEM to a minimum to avoid unnecessary complications to your naming convention. With the times that you do have to extend, ensure your entire team is onboard with a single strategy you're going to use throughout the project, and stick to it. Consistency is more important in my opinion. There's nothing worse than a codebase that doesn't conform to the original methodologies agreed.

Oh, and remember, nobody's going to judge you because you're not conforming with a specification to the tee. Most specifications are built on the extensions of others so if you have a use case then embrace it and look for a solution that suits yours and your project's needs.

If you have any other ideas on how you'd tackle grandchildren in BEM, I'd love to hear about them.

Until next time 

Follow us on Twitter, Facebook or Github.
© Copyright 2021 Assortment.
Created and maintained by Luke Whitehouse.