It doesn’t matter whether you learned about BEM just now, or follow it from the very beginning, you may have already appreciated such a useful methodology. If you do not know what BEM is, I recommend reading this to you on the BEM website before continuing to read this article, because I will use terms that imply a basic understanding of this CSS methodology.
This article is aimed at people who already use BEM and want to use it more effectively, as well as those who want to learn more about it.
Now I have no doubt about the way to call things. The only thing that repelled me for a long time was the ugly syntax. The designer inside of me didn’t want to clutter up my plush markup with horrible double underscores and hyphens.
The developer in me looked at it pragmatically. And, ultimately, the modular way to build the user interface outweighed the right side of my brain, which claimed: “But it’s not good enough!”. In such cases, I prefer the functionality over the external form. In any case, stop the empty talk. I present to your attention 10 problems that I have struggled with and a few tips on how to solve them.
For clarity, the grandchild selector is used when you need to refer to an element nested on two levels. These bad boys are a curse of my life, and I am sure that this is one of the reasons why people have an immediate aversion to BEM. I will give an example:
<div class="c-card"> <div class="c-card__header"> <!-- … --> <h2 class="c-card__header__title">Title text here</h2> </div> <div class="c-card__body"> <img class="c-card__body__img" src="http://some-img.png" alt="description"> <p class="c-card__body__text">Lorem ipsum dolor sit amet, consectetur</p> <p class="c-card__body__text">Adipiscing elit. <a href="/somelink.html" class="c-card__body__text__link">Pellentesque amet</a> </p> </div> </div>
As you can see, names can quickly go out of control and, the more nesting, the more ugly the class name of the element becomes. I used a short block called c-card
and short names body
, text
, link
, but try to imagine what happens if the initial block element is called c-drop-down-menu
.
I believe that a double underscore should appear only once, in the selector name. BEM stands for _--
, not ____--
. Also avoid multi-level naming of elements. If you have great-great-great-great-grandchildren levels, then you probably should reconsider the structure of the components.
BEM naming is not strictly bound to the DOM, so it does not matter how many levels the nested element is located. The naming convention primarily helps you see the relationship with the block at the top level - in our case, c-card
.
How I would consider the same card component:
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h2> </div> <div class="c-card__body"> <img class="c-card__img" src="http://some-img.png" alt="description"> <p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p> <p class="c-card__text">Adipiscing elit. <a href="/somelink.html" class="c-card__link">Pellentesque amet</a> </p> </div> </div>
This means that all nested elements will be affected only by the card unit. Also, we could move the text and images to c-card__header
or even add a new c-card__footer
element without breaking the semantic structure.
By this time, you may have noticed the use of c-
in my examples. This prefix is decoded as "component (component)" and underlies all the names of my BEM classes. The idea is borrowed from the Harry Robert technique , which improves the readability of the code.
The system I adopted and the many prefixes that will appear in the examples in this article:
Type of | Prefix | Examples | Description |
---|---|---|---|
Component | c- | c-card , c-checklist | Form the basis of the application and contain all the cosmetics for the individual components. |
Layout module | l- | l-grid , l-container | These modules do not have any cosmetics, they are used only for positioning the c- components and for building the application layout. |
Helpers | h- | h-show , h-hide | These classes have one function, often using !important to increase their specificity. (Mostly used for positioning or visibility.) |
States | is- , has- | is-visible , has-loaded | Shows the different states that c- can have. A more detailed description can be found below in problem 6. |
Javascript hooks | js- | js-tab-switcher | They indicate that the JavaScript behavior is tied to the component. Styles are not associated with them; used only to facilitate script manipulation. |
I found that such names have incredibly improved the readability of my code. Even if I fail to get you hooked on BEM, then this technique is definitely worth remembering.
You could also take other prefixes, such as qa-
for quality tests (assurance), ss-
for various server side hooks, etc. But the list above is a good start, and you can enter new names after you get comfortable with this technique.
You will see a good example of using this naming style in the next issue.
Some components require a parent wrapper (or container) that sets the layout of the child elements. In these cases, I always try to abstract the layout into a layout module, such as l-grid
and add each component as the contents of l-grid__item
.
In our card example, if we wanted to arrange a list of four c-card
s, I would use the following markup:
<ul class="l-grid"> <li class="l-grid__item"> <div class="c-card"> <div class="c-card__header"> […] </div> <div class="c-card__body"> […] </div> </div> </li> <li class="l-grid__item"> <div class="c-card"> <div class="c-card__header"> […] </div> <div class="c-card__body"> […] </div> </div> </li> <li class="l-grid__item"> <div class="c-card"> <div class="c-card__header"> […] </div> <div class="c-card__body"> […] </div> </div> </li> <li class="l-grid__item"> <div class="c-card"> <div class="c-card__header"> […] </div> <div class="c-card__body"> […] </div> </div> </li> </ul>
Now you should have a solid idea of how the module layout and component names should be combined together.
Do not be afraid to use a little more advanced markup, so that later you do not have a headache. No one will get better from the fact that you cut a couple of <div>
tags!
In some situations, this is not possible. If, for example, your grid is not going to obey you, or you just want some name with a meaning for your parent element, what should you do? I usually choose the word container
or list
, depending on the situation. By applying this to our card example, I could use <div class="l-cards-container">[…]</div>
or <ul class="l-cards-list">[…]</ul>
, depending on the case used. The key is to comply with your naming convention.
Another common problem is components whose styles or positioning are affected by the parent container. Various solutions to this problem are described in detail in Simurai . I will tell you only about the approach that I consider the most effective.
In short, let's assume that we want to add a c-button
to our card__body
from the previous example. This button is already its component and is framed as follows:
<button class="c-button c-button--primary">Click me!</button>
If you don’t have any differences between the usual button component, then there’s no problem, we’ll just move it like this:
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h3> </div> <div class="c-card__body"> <img class="c-card__img" src="http://some-img.png"> <p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p> <p class="c-card__text">Adipiscing elit. Pellentesque.</p> <!-- --> <button class="c-button c-button--primary">Click me!</button> </div> </div>
However, what happens when there are small differences in styles - for example, we want to reduce it a little, round the corners, but only when it is part of the c-card
component?
The cross component class seemed to me the most reliable solution:
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h3> </div> <div class="c-card__body"> <img class="c-card__img" src="http://some-img.png"> <p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p> <p class="c-card__text">Adipiscing elit. Pellentesque.</p> <!-- ** --> <button class="c-button c-card__c-button">Click me!</button> </div> </div>
This is what is known on the BEM website as "mix". However, I changed my attitude towards this approach, following some wonderful comments from Esteban Lussich.
In the example above, the c-card__c-button
class tries to change one or more existing c-button
properties, but depends on the source of the call (or even on the specifics) for successful application. The c-card__c-button
class will only work if it is defined after the c-button
block, which can quickly get out of control if you build more such cross-components. (Relying on !important
, of course, is possible, but I would not!).
The design of a truly modular UI element should be completely independent of the parent container — it should look the same no matter where you place it. Adding a class from another component to defining a style, as the “mix” method does, violates the open / closed principles of component-oriented design — that is, there should be no dependency on another module for aesthetics.
The best thing is to use the modifier for these small cosmetic differences, because you can easily use them again as your project grows.
<button class="c-button c-button--rounded c-button--small">Click me!</button>
Even if you do not use these additional classes again, at least you will not be tied to the parent container or to the original order to apply the changes.
Another way is to go to your designer and tell him that the buttons should be compatible with the rest, and avoid this problem altogether.
One of the biggest problems is deciding where the component ends and a new one begins. In the c-card
example, you could create another component called the c-panel
with very similar style attributes, but with very noticeable differences.
But what determines the necessity of using two components, c-panel
and c-card
, or a simple modifier for c-card
, which applies unique styles.
You can easily fill the project modules and do everything in the form of components. I recommend starting with modifiers, if it seems to you that managing any component CSS file becomes too complicated, it may be time to split some modifiers. A good indicator when you have to drop everything from the CSS block to style the new modifier is, as for me, it's time to create a new component.
Best of all, if you work in a team of other developers or designers, get their opinion. Collect them for a few minutes and discuss with them. Despite the fact that this answer looks more like an excuse, for large applications it is very important to understand which modules are available and what the component is.
This is a fairly common problem, especially when you style the component in an active or open state. Let's say that our cards are active; so, when we click on them, they stand out with a beautiful stroke. What about the name of this class?
You have two options: either use the autonomous state hook, or use BEM-like modifier naming at the component level:
<!-- --> <div class="c-card is-active"> […] </div> <!-- --> <div class="c-card c-card--is-active"> […] </div>
Although I like the idea of using the BEM name for consistency, the advantage of the stand-alone class is that it allows you to use JavaScript to apply universal hooks of a state to any component. When you have to apply a specific state class based on a modifier using a script, it becomes more problematic. This, of course, is possible, but requires writing additional JavaScript code.
It makes sense to stick with the standard set of state hooks. Chris Pierce put together a good list that I recommend to see.
I can understand people who are overwhelmed by the huge number of classes required to build a complex part of the user interface, especially if they did not assign a class to each tag.
I usually attach classes to everything that needs to be styled, in different ways, in the context of a component. I often leave p
tags without a class, unless a component requires them to look special in this context.
Granted, this will mean that your markup will contain many classes. But ultimately, your components will be able to live independently and be able to move without side effects.
Due to the global nature of CSS, assigning a class to everything gives us complete control over the render. The initial discomfort is worth the benefits of a fully modular system.
Let's say. that we want to display the checklist in our c-card
component. An example of how not to do it:
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h3> </div> <div class="c-card__body"> <p>I would like to buy:</p> <!-- --> <ul class="c-card__checklist"> <li class="c-card__checklist__item"> <input id="option_1" type="checkbox" name="checkbox" class="c-card__checklist__input"> <label for="option_1" class="c-card__checklist__label">Apples</label> </li> <li class="c-card__checklist__item"> <input id="option_2" type="checkbox" name="checkbox" class="c-card__checklist__input"> <label for="option_2" class="c-card__checklist__label">Pears</label> </li> </ul> </div> <!-- .c-card__body --> </div> <!-- .c-card -->
We have a few problems here. The first is a two-level selector, which we learned about in the first section. The second is that all styles applied to c-card__checklist__item
refer only to this particular case, which is why they cannot be used again.
I prefer to split the list into a markup module, checklist items into their own components, which allows them to be used independently in another place. This allows our l-
prefix to return to the game:
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h3> </div> <div class="c-card__body"><div class="c-card__body"> <p>I would like to buy:</p> <!-- - --> <ul class="l-list"> <li class="l-list__item"> <!-- --> <div class="c-checkbox"> <input id="option_1" type="checkbox" name="checkbox" class="c-checkbox__input"> <label for="option_1" class="c-checkbox__label">Apples</label> </div> </li> <li class="l-list__item"> <div class="c-checkbox"> <input id="option_2" type="checkbox" name="checkbox" class="c-checkbox__input"> <label for="option_2" class="c-checkbox__label">Pears</label> </div> </li> </ul> <!-- .l-list --> </div> <!-- .c-card__body --> </div> <!-- .c-card -->
This saves you from having to repeat these styles, and also means that we can use l-list
and c-checkbox
elsewhere in our application. This requires a bit more markup, but in return we get readability, encapsulation and reusability. You may have noticed that these are the main topics!
Some people think that the set of classes per element is bad, and, also, - --
can be added. For me personally, this does not seem to be a problem, because it makes the code more readable, and I know exactly what it should do.
An example of four classes with which the button is styled:
<button class="c-button c-button--primary c-button--huge is-active">Click me!</button>
I understand that this syntax is a bit ugly, but it is understandable.
However, if this makes your head start to hurt, you can take a look at Sergei Zarouski’s technique . Simply put, we need to use .className [class^="className"], [class*=" className"]
in the style sheet to simulate additional functionality. If this syntax seems familiar to you, it is because Icomoon uses a similar way to manage icon selectors.
With this technique, your code might look something like this:
<button class="c-button--primary-huge is-active">Click me!</button>
I don’t know if the performance loss is much greater when using class^=
and class*=
more than when using individual classes, but in theory, this is a cool alternative. For myself, I have enough of the option with several classes, but it seems to me that this method definitely deserves a mention for those who prefer the alternative.
This problem was put to me by Arie Thulank, and to which I tried to find a 100% solution.
An example would be a drop-down menu, which turns into a set of tabs at a given moment, or an off-screen side navigation, which turns into a menu bar.
In fact, one component can have two different states dictated by a media query.
For these two examples, I tend to simply make the c-navigation
component, since the change at a given moment is what it does. But it made me think, what about the lists of images that turn into a carousel on large screens? This is a problem case for me, and since it is well documented and commented, I think that ideally it’s worth creating a separate one-time component for this type of interface, with a clear name (such as c-image-list-to-carousel
).
Harry Roberts mentioned responsive prefixes , one of the ways to control this. His approach is designed more for changes in the layout and writing styles, rather than shifting all the components, but I see no reason why we cannot use this technique here. So you can call classes like this:
<ul class="c-image-list@small-screen c-carousel@large-screen">
In the future, it will live in your media queries for the appropriate screen sizes.
Tip: You will have to disable @
using a backslash, like this:
.c-image-list\@small-screen { /* */ }
I had no reason to create such components, but this method looks very friendly to the developer. It should be enough for another person to simply understand your intentions. But I do not advocate such names as small-screen
and large-screen
- they are used only to improve readability.
BEM turned out to be a salvation for me in my desire to create modular applications in a component way. I have been using it for almost 3 years now, and the problems listed above have been stumbling blocks in my path. I hope that this article will seem useful to you, and if you are not yet using BEM, I highly recommend you to start it.
Write about all errors (grammatical, lexical, etc.) in the comments, I will be glad to correct them. Thanks for attention
Source: https://habr.com/ru/post/305548/
All Articles