cssbemsmacss

SMACSS and BEM: How to position Module inside of a Module?


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>

Solution

  • 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:

    Approach 1

    This is clearly the worst approach and has several flaws:

    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.