Sass, BEM and @extend
In my personal projects I love using vanilla CSS3 and Scss preprocessor. My Scss architecture is inspired by 7-1 architecture, which means that each folder contains partials, and we have a single file at the root level (main.scss) which imports them all to be compiled into a CSS stylesheet.
Right now, I'm working on a project that is more complex than others I've worked on. I'm using BEM methodology and I often find myself in a situation where the same modifier class is being used by multiple block or element classes which is a perfect case for using the @extend
directive.
What is @extend
?
@extend
is a directive that lets us to share or to inherit a set of CSS properties from one selector to another. We'll do a simple example of using this directive:
// this class selector will be used
// by multiple other selectors
// it is called extending selector
.shared-styles {
padding: 10px;
background-color: blue;
}
// these are extended selectors
.selector-one,
.selector-two,
.selector-three {
@extend .shared-styles;
}
After compilation, the CSS code looks like this:
.shared-styles,
.selector-one,
.selector-two,
.selector-three {
padding: 10px;
background-color: blue;
}
Using %placeholders
In our example, we will never use the .shared-styles
class on its own, which makes the perfect case for using a %placeholder
selector.
// instead of css class selector
// we now use %placeholder selector
%shared-styles {
padding: 10px;
background-color: blue;
}
.selector-one,
.selector-two,
.selector-three {
@extend %shared-styles;
}
After compilation, the CSS code now looks like this:
.selector-one,
.selector-two,
.selector-three {
padding: 10px;
background-color: blue;
}
We can see that the placeholder rule itself has not been compiled to css.
Important tips for using @extend
If we're not careful when using @extend
directive it can lead to negative side effects and bloated code. Always keep in mind these rules:
- Use
@extend
only for maintaining relationships within selectors. If two selectors are characteristically similar, use@extend
. If they are unrelated but share some rules, use@mixin
. - Use
@extend
on%placeholders
primarily, not on actual selectors. - When extending classes, only extend a class with another class, never a complex selector.
- For inheriting styles only use
@extend
if the extending placeholder selector is a kind of the extended selector.
My example of using BEM and @extend
Let's say, I'm building my custom select component instead of using default HTML <select>
element. My HTML and Scss code would look something like this:
HTML:
<div class="select-single_primary" role="listbox">
<button class="select-single__btn-toggle_primary">
Select an Option
</button>
<ul class="select-single__menu_primary" role="presentation">
<li class="select-single__option_primary">Option one</li>
<li class="select-single__option_primary">Option two</li>
</ul>
</div>
SCSS
// Placeholders
%primary-styles {
background-color: #fafafa;
box-shadow: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2),
-0.5px -0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2);
color: #323232;
border-radius: 0.5rem;
font-size: 1.5rem;
min-width: 12rem;
padding: 1rem;
}
%select-single {
position: relative;
}
%select-single-toggle {
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
border: none;
}
%select-single-menu {
position: absolute;
top: 125%;
left: 0;
display: flex;
flex-direction: column;
gap: 1rem;
}
%select-single-option {
list-style: none;
cursor: pointer;
border-radius: 0.5rem;
padding: 0.5rem;
}
// Block
.select-single {
@extend %select-single;
// Modifier
&_primary {
@extend %select-single;
}
// Element
&__btn-toggle {
@extend %select-single-toggle;
// Modifier
&_primary {
@extend %select-single-toggle;
@extend %primary-styles;
font-weight: 500;
&:hover {
background-color: #ebebeb;
}
}
}
// Element
&__menu {
@extend %select-single-menu;
// Modifier
&_primary {
@extend %select-single-menu;
@extend %primary-styles;
}
}
// Element
&__option {
@extend %select-single-option;
// Modifier
&_primary {
@extend %select-single-option;
&[aria-selected="true"] {
background-color: #ebebeb;
}
&:hover {
background-color: #ebebeb;
}
}
}
}
The CSS output looks like this:
.select-single__btn-toggle_primary, .select-single__menu_primary {
background-color: #fafafa;
box-shadow: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2), -0.5px -0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2);
color: #323232;
border-radius: 0.5rem;
font-size: 1.5rem;
min-width: 12rem;
padding: 1rem;
}
.select-single, .select-single_primary {
position: relative;
}
.select-single__btn-toggle, .select-single__btn-toggle_primary {
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
border: none;
}
.select-single__menu, .select-single__menu_primary {
position: absolute;
top: 125%;
left: 0;
display: flex;
flex-direction: column;
gap: 1rem;
}
.select-single__option, .select-single__option_primary {
cursor: pointer;
border-radius: 0.5rem;
padding: 0.5rem;
}
.select-single__btn-toggle_primary {
font-weight: 500;
}
.select-single__btn-toggle_primary:hover {
background-color: #ebebeb;
}
.select-single__option_primary[aria-selected="true"] {
background-color: #ebebeb;
}
.select-single__option_primary:hover {
background-color: #ebebeb;
}
Learning Through Writing This Article
While checking the CSS output, I noticed several CSS class selectors that are actually not being used.
Those are the ones without a modifier like
- select-single,
- select-single__btn-toggle,
- select-single__menu etc.
In this case we can further reduce our CSS code by removing @extend
directive for these classes!
After doing this, our SCSS code looks like this:
// Block
.select-single {
// Modifier
&_primary {
@extend %select-single;
}
// Element
&__btn-toggle {
// Modifier
&_primary {
@extend %select-single-toggle;
@extend %primary-styles;
font-weight: 500;
&:hover {
background-color: #ebebeb;
}
}
}
// Element
&__menu {
// Modifier
&_primary {
@extend %select-single-menu;
@extend %primary-styles;
}
}
// Element
&__option {
// Modifier
&_primary {
@extend %select-single-option;
&[aria-selected="true"] {
background-color: #ebebeb;
}
&:hover {
background-color: #ebebeb;
}
}
}
}
And CSS output:
.select-single__btn-toggle_primary, .select-single__menu_primary {
background-color: #fafafa;
box-shadow: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2), -0.5px -0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2);
color: #323232;
border-radius: 0.5rem;
font-size: 1.5rem;
min-width: 12rem;
padding: 1rem;
}
.select-single_primary {
position: relative;
}
.select-single__btn-toggle_primary {
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
border: none;
}
.select-single__menu_primary {
position: absolute;
top: 125%;
left: 0;
display: flex;
flex-direction: column;
gap: 1rem;
}
.select-single__option_primary {
cursor: pointer;
border-radius: 0.5rem;
padding: 0.5rem;
}
.select-single__btn-toggle_primary {
font-weight: 500;
}
.select-single__btn-toggle_primary:hover {
background-color: #ebebeb;
}
.select-single__option_primary[aria-selected="true"] {
background-color: #ebebeb;
}
.select-single__option_primary:hover {
background-color: #ebebeb;
}
Conclusion
That's it for this article. Of course, it's always possible that I got something wrong, so I would appreciate feedbacks!
Resources
- Official Sass Website
- https://sass-guidelin.es/#extend
- w3 schools article on extend
Subscribe to my newsletter
Read articles from Amer directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by