BEM approach for an element than can belong and look different depending on the Block?
Usually with BEM if they look different, they are different.
Usually.
There are a number of different choices for handling context and state with BEM. Each has different pros and cons, so which you use will depend heavily on your use case.
The first option I'll mention is to use descendant selectors. You've already identified this choice, and are running into the usual problem of "where does the code belong?"
For the following examples, I'm going to rely on LESS syntax, this is only to make it easier for me to demonstrate relationships in the code.
Descendant Selector
If you're going to use a descendant selector, I recommend that the code be grouped with the child block.
widget.less.widget {
&__container {
...
}
...
}
checkbox.less
.checkbox {
&__label {
...
}
...
// using inversion to override checkbox styles in a widget
// this will render to `.widget .checkbox` instead of
// `.checkbox .widget` due to the `&` placement
.widget & {
}
}
The reason I recommend associating the styles with the inner block is because the styles will affect the checkbox, and the cascade order will be important.
If the styles were associated with the parent, reordering the parent styles relative to the child styles could adversely affect how the styles render.
Consider this inclusion order:
site.less@import 'widget';
@import 'checkbox';
If the styles were part of the widget, they could be overridden by a selector of equal specificity in checkbox.less
.
Modifiers
I recommend using modifiers for state. I don't generally consider position or context to be "state", so modifiers may not be appropriate. Additionally, multiple modifiers on the same element can be difficult to reason about and therefor difficult to style.
Assuming you're not using a modifier on the checkbox
block, then it may be simpler to add the modifier for the case where it's used in a panel.
.checkbox {
&__label {
...defaults...
}
...defaults...
&--alt {
&__label {
...overrides...
}
...overrides...
}
}
Of course, this requires that the markup be updated for the particular case where it's used in a panel, but then it also opens you up to using the checkbox with the same styles elsewhere.
Different Selector
I'm going to reiterate my first point: If they look different they are different.
This doesn't mean you have to start from scratch on the checkbox. BEM allows for object oriented styles. Come up with a new name, and extend* the checkbox:
checkbox.less.checkbox {
&__label {
...
}
...
}
checkbox-2.less
@import (reference) 'checkbox';
.checkbox-2 {
.checkbox;
&__label {
...overrides...
}
...overrides...
}
* in LESS I'm using a mixin for this as it's generally better suited toward extending and overriding styles than using the :extend
feature of the language. Feel free to use the :extend
feature, just be aware that selector order will matter.
Refactor the Need Away
Sometimes I run into cases where I want to use a descendant selector or modifier because I need to bump a block for positioning purposes in a container.
In these cases, I often find that the container itself is what should be changed. I can usually tell that it's the container when I need to update the child to have different:
- margins
- padding
- position
- top, right, bottom, left
- width
- height
- flex
Refactoring comes with other challenges, however I often end up using container divs to normalize the insertion region for blocks that contain other blocks; YMMV.
tl;dr: Which Should I Pick?
Can you (reasonably) update the markup?
YES: If you can easily update the markup to use different classes, I'd recommend extending your checkbox
block as a new block. Naming things is hard though, so be sure to document which one is which somewhere.
NO: If you can't easily update the classes, using modifiers wouldn't be a great choice either. I'd recommend skipping that one, and falling back to the good ol' descendant selector. In BEM you really want to avoid descendant selectors, but sometimes they're the right tool for the job.