Skip to content

Instantly share code, notes, and snippets.

@radist2s
Created March 25, 2019 15:07
Show Gist options
  • Save radist2s/0b74fb70d3cf4cc4a9baaf72921f2d41 to your computer and use it in GitHub Desktop.
Save radist2s/0b74fb70d3cf4cc4a9baaf72921f2d41 to your computer and use it in GitHub Desktop.
BEM & SASS best practices

BEM & SASS best practices

Every block should be in separated file named as block.

Filename: rating-star.scss

.rating-star {
    $font-size: 0.5em;
    
    display: inline-block; // `display` style may be set freely
    width: $font-size;
    height: $font-size;

    // do not override all the properties with short syntax if you don't really need it
    background-color: antiquewhite;
    // Do you need to change background color only? Use `background-color`
    // Do you need to change `margin` to `auto` at `left` and `right`? Use verbose syntax: `margin-left: auto; margin-right: auto;`
    // But of course, feel free to do: `margin: 0 1em 0.3em` if you are really need it.

    border-radius: 100%;

    // Adds `modifier` for `block`
    &--highlighted {
        background-color: yellow;
    }
}

Filename: product-rating.scss

.product-rating {
    // `font-size` property is prohibited for block with children (means with `elements`)
    // Every `block` containing `elements` should inherit `font-size` from its parent

    display: flex;
    justify-content: center;
    align-items: center;

    &__caption {
        font-style: italic;
        margin-right: 0.5em;

        // Caption is ready to be shown with siblings, and as stand alone caption without trailing margin
        &:last-child {
            margin-right: 0;
        }
    }
}

Filename: product-item.scss

.product-item {
    $b-root: &;

    display: flex;
    background-color: red;

    // `margin` and `padding` is prohibited for `blocks` at all
    // `margin` and `padding` must be set only via cascade (from parent `block` or `element`, not by current block itself)

    &__title {
        font-size: 2em; // only `em` values should be used wherever possible
        // Fixed sizes (px, rem) have to be used only if you are really understand what are you going to do

        margin-bottom: 0.3em; // don't use `padding` to add offset between nodes
        // Btw, for some one could be new to know about margin collapsing
        // More info here: https://css-tricks.com/what-you-should-know-about-collapsing-margins/

        // We can use `block` variable to create cascade in this case
        & + #{$b-root}__title-secondary {
            margin-top: 1em;
        }
    }

    // One more possible way to keep `block` name for `elements`
    &__title + &__title-secondary {
        margin-top: 1em;
    }

    &__title-secondary {
        font-size: 1.3em;
    }

    .product-rating {
        justify-self: flex-end;
        font-size: 1.1em;
    }

    &--rating-pull-left {
        .product-rating {
            justify-self: flex-start;
        }
    }
}

HTML markup:

<div class="product-item product-item--rating-pull-left">
    <h2 class="product-item__title">Product Title</div>
    <div class="product-rating">
        <div class="rating-star rating-star--highlighted"></div>    
        <div class="rating-star rating-star--highlighted"></div>    
        <div class="rating-star rating-star--highlighted"></div>    
        <div class="rating-star"></div>    
    </div>
</div>

CSS output will be:

.rating-star {
  display: inline-block;
  width: 0.5em;
  height: 0.5em;
  background-color: antiquewhite;
  border-radius: 100%;
}

.rating-star--highlighted {
  background-color: yellow;
}

.product-rating {
  display: flex;
  justify-content: center;
  align-items: center;
}

.product-rating__caption {
  font-style: italic;
  margin-right: 0.5em;
}

.product-rating__caption:last-child {
  margin-right: 0;
}

.product-item {
  display: flex;
  background-color: red;
}

.product-item__title {
  font-size: 2em;
  margin-bottom: 0.3em;
}

.product-item__title + .product-item__title-secondary {
  margin-top: 1em;
}

.product-item__title + .product-item__title-secondary {
  margin-top: 1em;
}

.product-item__title-secondary {
  font-size: 1.3em;
}

.product-item .product-rating {
  justify-self: flex-end;
  font-size: 1.1em;
}

.product-item--rating-pull-left .product-rating {
  justify-self: flex-start;
}
Blocks Modifiers in separated files

Filename also could be as product-rating--inverted.scss (suffix with modifier name) and contain only block modifier`s code.

Filename: product-rating.scss

.product-rating {
    // common styles here
}

Filename: product-rating--inverted.scss

.product-rating--inverted {
    // inverted styles here
}

Most wrong syntax

Wrong example

Filename: product-item.scss

.product-item {
    
    // Actually, it's not about syntax.
    // But don't forget, this is bad way
    font-size: 14px; // if really needed at least convert it to `em`

    &--title {
        font-size: 2em;
        
        &-secondary { // never do this, impossible to debug
            font-size: 1.3em;
        }
    }
}
Correct example

Filename: product-item.scss

.product-item {
    &--title {
        font-size: 2em;
    }
    
    &--title-secondary {
        font-size: 1.3em;
    }
}

Misunderstood of BEM logic

In this case we need to set different background-color for .rating-star, for normal and highlighted states.

Take a look on __iverted modifier:

<div class="product-rating product-rating__inverted">
    <div class="rating-star rating-star--highlighted"></div>    
    <div class="rating-star rating-star--highlighted"></div>    
    <div class="rating-star"></div>    
</div>

Filename: product-rating--inverted.scss

.product-rating--inverted {
    .rating-star { // yes, allowed to use cascade for inner `block`, not a mistake
        background-color: black; // everything is still ok
        // We can freely override child `block` from parent `block` or even from `element`
        
        &--highlighted {
            background-color: black; // wrong usage
            // Never change style for block `modifier` directly from parent `block`
        }
    }
}

To resolve this, we need to change html markup and .rating-star block.

<div class="product-rating">
    <div class="rating-star rating-star--inverted rating-star--highlighted"></div>    
    <div class="rating-star rating-star--inverted rating-star--highlighted"></div>    
    <div class="rating-star"></div>    
</div>

Filename: rating-star.scss

.rating-star {
    $b-root: &; // store pointer to root `block` selector

    background-color: antiquewhite;

    &--highlighted {
        background-color: yellow;
    }
    
    &--inverted {
        background-color: white;
        opacity: 0.5;
    }
    
    &--highlighted#{&}--inverted { // same as rule below; `#{&}` is important in this case/ 
        opacity: 1;
    }
    
    #{$b-root}--highlighted#{$b-root}--inverted {
        // same selector as above in different notation
    }
}

CSS output will be

.rating-star {
  background-color: antiquewhite;
}
.rating-star--highlighted {
  background-color: yellow;
}
.rating-star--inverted {
  background-color: white;
  opacity: 0.5;
}
.rating-star--highlighted.rating-star--inverted {
  opacity: 1;
}

Actually more BEM-way to do this is to declare completely new modifier:

.rating-star {
    &--inverted {
        background-color: white;
        opacity: 0.5;
    }
    
    &--highlighted-inverted { // same as rule below; `#{&}` is important in this case/ 
        opacity: 1;
    }
}

And html markup will be:

<div class="product-rating">
    <!-- This `modifier` naming also could be used, -->
    <!-- But it will be painful to interact with this `block` by JS without two way binding (react, vue) -->
    <div class="rating-star rating-star--highlighted-inverted"></div>
    <div class="rating-star rating-star--highlighted-inverted"></div>    
    <div class="rating-star"></div>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment