15

Note: I use the word Module which in BEM is called a Block. Also using modified BEM naming convention BLOCK__ELEMENT--MODIFIER, please use that in your answer as well.


Suppose I have a .btn module that looks something like this:

.btn {
  background: red;
  text-align: center;
  font-family: Arial;

  i {
    width:15px;
    height:15px;
  }
}

And I need to create a .popup-dialog module with a .btn inside of it:

.popup-dialog {
  ...
  .btn {
    position: absolute;
    top: 10px;
    right: 10px;
  }
}

In SMACSS and BEM, how should you handle positioning a module inside of a module?


In your answer, please identify the correct solution, and analyze the following approaches as well: (note that all of the examples below build upon or modify the above CSS)


Approach 1

[ override the original .btn class inside of .popup-dialog ]

CSS:

.popup-dialog {
  ...
  .btn {  // override the original .btn class
    position: absolute;
    top: 10px;
    right: 10px;
  }
}

Markup:

<div class="popup-dialog">
  ...
  <button class="btn"><i class="close-ico"></i> close</btn>
</div>

Approach 2

[ add a subclass inside of .popup-dialog ]

CSS:

.popup-dialog {
  ...
  .popup-dialog__btn {
    position: absolute;
    top: 10px;
    right: 10px;
  }
}

Markup:

<div class="popup-dialog">
  ...
  <button class="btn popup-dialog__btn"><i class="close-ico"></i> close</btn>
</div>

Approach 3

[ subclass .btn with a modifier ]

CSS:

.btn--dialog-close {
  position: absolute;
  top: 10px;
  right: 10px;
}

Markup:

<div class="popup-dialog">
  ...
  <button class="btn btn--dialog-close"><i class="close-ico"></i> close</btn>
</div>

Approach 4

[ subclass .btn with a layout class ]

CSS:

.l-dialog-btn {       // layout
  position: absolute;
  top: 10px;
  right: 10px;
}

Markup:

<div class="popup-dialog">
  ...
  <button class="btn l-dialog-btn"><i class="close-ico"></i> close</btn>
</div>
Gil Birman
  • 35,242
  • 14
  • 75
  • 119

4 Answers4

18

Having struggled with the issue in a recent large-scale project myself, I applaud you to bringing this to attention on SO.

I'm afraid that there's not a single 'correct' solution to the problem, and it's going to be somewhat opinion-based. However I will try to be as objective as possible and give some insight in regard to your four approaches on what worked for my team and what didn't.

Also I'm going the assume the following:

  • You're familiar with the SMACCS approach (you read the book and implemented it in at least one project).
  • You're using only the (modified) BEM naming convention for your CSS classnames, but not the rest of BEM methodology development stack.

Approach 1

This is clearly the worst approach and has several flaws:

  • It creates a tight coupling between .popup-dialog and .btn by using context-based selectors.
  • You are likely going to run into specificity issues in the future, supposed you will add additional .btn elements in the .popup-dialog in the future.

If somehow you'd need to use classnames unaltered, I'd suggest at least reducing the depth of applicability by using direct child selectors.

CSS:

.popup-dialog {...}

.popup-dialog > .btn {
  position: absolute;
  top: 10px;
  right: 10px;
}

Approach 2

This is actually quite close to our solution. We set the following rule in our project and it proved to be robust: "A module must not have outer layout, but may layout its submodules". This is in heavily inspired by @necolas conventions from the SUITCSS framework. Note: We're using the concept, not the syntax.

https://github.com/suitcss/suit/blob/master/doc/components.md#styling-dependencies

We opted for the second option here and wrap submodules in additional container elements. Yes, it creates more markup, but has the benefit that we can still apply layout when using 3rd party content where we can't control the HTML (embeds from other sites, etc.).

CSS:

.popup-dialog {...}

.popup-dialog__wrap-btn {
  position: absolute;
  top: 10px;
  right: 10px;
}

HTML:

<div class="popup-dialog">
  ...
  <div class="popup-dialog__wrap-btn">
    <button class="btn"><i class="close-ico"></i> close</button>
  </div>
</div>

Approach 3

This might seem clean (extends instead of overwrites), but isn't. It mixes layout with module styles. Having layout styles on .btn--dialog-close will not be useful in the future if you have another module that needs to have a different layout for a close button.

Approach 4

This is essentially the same as approach 3 but with different syntax. A layout class must not know about the content it lays out. Also I'm not to keen on the l-prefix syntax suggested in the book. From my experience it creates more confusion than it helps. Our team dropped it completely and we just treat everything as modules. However If I needed to stick with it, I'd try to abstract the layout completely from the module, so you have something useful and re-usable.

CSS:

.l-pane {
  position: relative;
  ...
}

.l-pane__item {
  position: absolute;
}

.l-pane__item--top-right {
  top: 10px;
  right: 10px;
}

.popup-dialog { // dialog skin
  ...
}

.btn { // button skin
  ...
}

HTML:

<div class="popup-dialog l-pane">
  <div class="l-pane__item l-pane__item--top-right">
    <button class="btn"><i class="close-ico"></i> close</button>
  </div>
</div>

I wouldn't fault anyone for this approach, but from my experience not all layouts can be abstracted in a reasonable manner and have to be set individually. It also makes it harder for other developers to understand. I'd exclude grid layouts from that assumption, they're easy enough to grasp and very useful.

There you have it. I'd suggest trying the modified Approach 2 for the reasons stated above.

Hoping to help.

mlnmln
  • 575
  • 2
  • 10
  • Thank you for the fascinating and clear answer. I'm curious, what do you dislike about the SUITECSS syntax? – Gil Birman Jul 19 '14 at 15:51
  • Actually I was referencing the _layout-__ or _l-_ prefix as proposed by SMACCS. As much as the book has done, I think the layout part is way too ambiguous, hence creating confusion. This is largely based on the two use cases show. Repeating page elements (l-header, l-footer, l-sidebar) and _true_ layout classes (l-grid), which are different things and should be treated as such. – mlnmln Jul 19 '14 at 16:17
  • SUITCSS and its syntax on the other hand seem mature to me. We're just can not afford to change our current build process. We're using SASS, but SUIT requires a different preprocessor (based on node.js as far as I know). That said, adopting the syntax alone would be possible, but is not currently high up on our list. – mlnmln Jul 19 '14 at 16:28
  • I just had to give you credit for this amazing answer. I've been pondering on that issue for day and you have articulated it perfectly. Keep it up! – Boris Litvinsky Jun 18 '15 at 11:18
2

BEM

If you don't modify .btn inside .popup-dialog first approach is the best.

If you need some .btn modifications, according to BEM methodology you have to use modificator class, like .btn_size_s

If you have modification not directlly connected with .btn, and you doubt whether may be reusable in future, for example you have to float .btn to right only in popup, you can use mixin like .popup-dialog__btn

SMACSS

Again if you need just place one block inside other -follow first approach.

If you need any modifications, there 2 ways : subclasses and descendant selectors.

If you modification may be reused in future - use subclasses, like .btn-size-s. If modification is tightly connected with some specific module - better use descendant selectors.

UPDATE:

Add few points to clear my answer:

Firstly, Approach 4 is unacceptable - you mix module with layout it is bad practice, as Layout classes in charge of grids and page sections geometry, Module is independent from Layout and should know nothing about section it placed.

Now let me comment other approach and what is the best usage of it:

Approach 1 - Consider following case: You have Popup Module with 'close' Button Module. Popup do nothing with Button, no modification, no floats or margings, its just its child. This is the case this approach is best.

Approach 2 - Another case: Popup has child Button, but we have to add extra top margin and float Button to the right. As you can see this modification tightly coupled with Popup, and cant be usefull for other modules. Such 'local' modifications best usage of this approach. in BEM this approach as also known as mix

Approach 3 - Final case: Popup with child Button, but we need bigger Button, such modificated button can be reused and may be usefull for other Modules and pages. In BEM its called modifier

To mark main difference between A2 and A3, lets remove Button from Popup and place it somewhere else . A3 will still affect Button, A2 not.

So to work with module as child you can use A1 or A2, A3 should be used in case on module modification independently from context.

Evgeniy
  • 2,915
  • 3
  • 21
  • 35
  • Thanks, but I don't really understand your answer. You say to use a modifier, but you don't say where to put it (inside .popup-dialog or as part of .btn module). Also naming a modifier `.btn_size_s` doesn't make sense using the naming style I used in my question which follows BLOCK__ELEMENT--MODIFIER convention. – Gil Birman Jul 15 '14 at 14:56
  • modifier is always part of the block, so modifier of btn should be in btn. You cant use popup modifier to change btn. second, i've used as an example original bem naming convention to define modifier. – Evgeniy Jul 15 '14 at 15:59
  • Is `size_s` the modifier? If so, shouldn't it be `.btn--size_s` ? – Gil Birman Jul 15 '14 at 19:56
  • Yep, its modifier, and according to bem naming convention should be defined with lodash as separators name_value – Evgeniy Jul 16 '14 at 03:23
  • OK thanks, confusing answer but +1 for being somewhat helpful. I am actually using modified BEM naming convention. – Gil Birman Jul 17 '14 at 00:29
0

There is another convention that maybe suits your needs: https://henryruhs.gitbook.io/ncss

Goal:

A predictable grammar for CSS that provides semantic information about the HTML template.

  • What tags, components and sections are affected
  • What is the relation of one class to another

Example:

<div class="modal-dialog">
  ...
  <div class="wrapper-button-dialog">
    <button class="button-dialog">close</button>
  </div>
</div>
Henry Ruhs
  • 1,533
  • 1
  • 20
  • 32
0

First off, I want to clarify that a button, by definition in BEM, is an ELEMENT not a BLOCK. So if you were to tackle this problem using the BEM methodology then this issue becomes a bit simpler.

Secondly, I agree with mlnmln's solution (Approach 2) as it defines the element variation within the block, which is unique to the block itself. However, if an element variation like this button exists outside of the popup-dialog block, then you would want to take Approach 3 and apply a naming convention that allows for global usage.

kretzm
  • 762
  • 1
  • 7
  • 14