bem-sass
v1.0.0
Published
a Sass library for organizing CSS objects
Downloads
10
Maintainers
Readme
bem-sass
bem-sass
is a Sass library for organizing CSS objects. It helps you apply BEM architecture to CSS you write with no compatibility issues with both RubySass(>= 3.4) and LibSass(>=3.3).
bem-sass
is heavily inspired by Immutable CSS and ITCSS as well as the original BEM methodology. It is a pure Sass implementation of those concepts.
Quick Start
- Install with Bower:
bower install bem-sass --save-dev
- Install with npm:
npm install bem-sass --save-dev
Basic Usages
Once you import bem-sass
to your project, you can simply build your own css object like below:
// Menu block
@include block(menu) {
/*...the menu block styles are here...*/
@include element(item) {
/*...the menu item styles are here...*/
}
@include modifier(horizontal) {
/*...the horizontal menu styles are here...*/
}
}
When compiled:
.menu {
/*...the menu block styles are here...*/
}
.menu__item {
/*...the menu item styles are here...*/
}
.menu_horizontal {
/*...the horizontal menu styles are here...*/
}
Configurations
You can configure bem-sass options by configure-bem-sass
mixin. Using this mixin is optional. If there have been no custom configurations, the default options are exactly the same as below:
@include configure-bem-sass ((
default-prefix: "",
block-levels: (),
element-sep: "__",
modifier-sep: "_"
));
default-prefix
Set the default prefix for block
mixin.
@include configure-bem-sass((
default-prefix: "b-" // Set default block prefix to "b-"
));
/* Menu block */
@include block(menu) {
/*...styles are here...*/
@include element(item) {
/*...styles are here...*/
}
}
When compiled:
/* Menu block */
.b-menu {
/*...styles here...*/
}
.b-menu__item {
/*...styles here...*/
}
block-levels
Sometimes you may need to define several block types to organize your css object structure especially when you are considering a methodology like ITCSS. You can define your own several block levels by adding level-name(string): prefix(string)
to block-levels
map.
@include configure-bem-sass((
block-levels: (
object: "o-",
component: "c-"
)
));
/* Media object */
@include block(media, "object") {
/*...styles are here...*/
}
/* Menu component */
@include block(menu, "component") {
/*...styles are here...*/
}
When compiled:
/* Media object */
.o-media {
/*...styles are here...*/
}
/* Menu component */
.c-menu {
/*...styles are here...*/
}
element-sep
, modifier-sep
You can set your own separators for element and modifier respectively.
@include configure-bem-sass((
// Set separators like Medium.com
element-sep: "-",
modifier-sep: "--"
));
/* Promo block */
@include block(promo) {
/*...styles are here...*/
@include element(title) {
/*...styles are here...*/
}
@include modifier(hero) {
/*...styles are here...*/
}
}
When compiled:
/* Promo block */
.promo {
/*...styles are here...*/
}
.promo-title {
/*...styles are here...*/
}
.promo--hero {
/*...styles are here...*/
}
Extended Details
Boolean Modifier & Key-Value Modifier
bem-sass supports key-value modifiers. When using modifier
, passing a single argument generates a boolean modifier, whereas passing 2 arguments generates a key-value modifier.
// @see https://en.bem.info/method/naming-convention/#block-modifier
@include block(menu) {
/* Boolean modifier */
@include modifier(hidden) {
/*...the hidden menu styles are here...*/
}
/* key-value modifiers */
@include modifier(theme, morning-forest) {
/*...the morning-forest themed menu styles are here...*/
}
@include modifier(theme, stormy-sky) {
/*...the stormy-sky themed menu styles are here...*/
}
}
/* Boolean modifier */
.menu_hidden {
/*...the hidden menu styles are here...*/
}
/* key-value modifiers */
.menu_theme_morning-forest {
/*...the morning-forest themed menu styles are here...*/
}
.menu_theme_stormy-sky {
/*...the stormy-sky themed menu styles are here...*/
}
Element Modifier
Elements could also get modified by their own modifiers.
// @see https://en.bem.info/method/naming-convention/#element-modifier
@include block(menu) {
@include element(item) {
/* Boolean modifier */
@include modifier(visible) {
/*...the visible menu item styles are here...*/
}
/* key-value modifier */
@include modifier(type, radio) {
/*...the radio type menu item styles are here...*/
}
}
}
/* Boolean modifier */
.menu__item_visible {
/*...the visible menu item styles are here...*/
}
/* key-value modifier */
.menu__item_type_radio {
/*...the radio type menu item styles are here...*/
}
Using Cascades in BEM
// @see https://en.bem.info/method/solved-problems/#using-cascades-in-bem
/* Nav block */
@include block(nav) {
/*...the default nav styles are here...*/
@include element(item) {
/*...the default nav item styles are here...*/
}
@include modifier(theme, islands) {
/*...the islands themed nav styles are here...*/
@include element(item) {
/*...the islands themed nav item styles are here...*/
}
}
}
When compiled:
/* Nav block */
.nav {
/*...the default nav styles are here...*/
}
.nav__item {
/*...the default nav item styles are here...*/
}
.nav_theme_islands {
/*...the islands themed nav styles are here...*/
}
.nav_theme_islands > .b-nav__item {
/*...the islands themed nav item styles are here...*/
}
Adjacent Sibling Elements in a Modifier
Given that you want to add a top line to each item of a modified nav block except the first item. With &
provided by the original Sass, you cannot achieve this requirement. In that kind of circumstance, you can use adjacent-siblings
mixin.
@include block(nav) {
@include modifier(secondary) {
@include element(item) {
// Using & + & produces `.nav_secondary .nav__item + .nav_secondary .nav__item` which we do not expect.
// Use `adjacent-siblings` here.
@include adjacent-siblings {
border-top: 1px solid rgb(0, 0, 0);
}
}
}
}
When compiled:
.nav_secondary > .nav__item + .nav__item {
border-top: 1px solid rgb(0, 0, 0);
}
Shared CSS Rules Between Elements
Given that nav__item
and nav__link
have common CSS rules. Since bem-sass enforces immutability on every BEM entity, it seems that the only way to avoid an inevitable code duplication is using Sass placeholder and @extend
.
@include block(nav) {
%shared-rules { // <--- BAD: A placeholder inside block generates unwanted nested selectors
display: inline-block;
height: 100%;
}
@include element(item) {
@extend %shared-rules;
}
@include element(link) {
@extend %shared-rules;
}
}
But when compiled, this produces unexpected nested selectors like below:
.nav .nav__item,
.nav .nav__link {
display: inline-block;
height: 100%;
}
To avoid this, bem-sass provides def-shared-rules
and shared-rules
.
@include block(nav) {
@include def-shared-rules("items") {
display: inline-block;
height: 100%;
}
@include element(item) {
@include shared-rules("items");
}
@include element(link) {
@include shared-rules("items");
}
}
.nav__item,
.nav__link {
display: inline-block;
height: 100%;
}
Note that def-shared-rules
and shared-rules
should be inside of a block.
Caveats
Element and Modifier Cannot be used Stand-Alone
An element(or a modifier) is a part of a block. It has no standalone meaning without it's parent block.
// @see https://en.bem.info/method/key-concepts/#element
@include element(item) { // <-- BAD: element without it's block
/*...CSS declarations here...*/
}
// @see https://en.bem.info/faq/#how-do-i-make-global-modifiers-for-blocks
@include modifier(theme, islands) { // <- BAD: modifier without it's block
/*...CSS declarations here...*/
}
When compiled:
Error: element should be inside of a block
Error: modifier should be inside of a block
Avoid Elements Within Elements
The existence of elements of elements is an antipattern because it hinders the ability to change the internal structure of the block. bem-sass prevents you from creating those kind of invalid elements.
// @see https://en.bem.info/faq/#why-does-bem-not-recommend-using-elements-within-elements-block__elem1__elem2
@include block(nav) {
@include element(item) {
@include element(link) { // <--- BAD: Attempt to make an element within another element
}
}
}
Error: element should not be within another element
Keep BEM Entities Immutable
bem-sass ensures that every BEM entity you create is immutable. It prevents you from reassigning css classes which in turn produces side effects.
// Nav block
@include block(nav) {
/*...CSS declarations here...*/
@include element(item) {
/*...CSS declarations here...*/
}
@include element(item) { // <--- BAD: Attempt to reassign the nav item styles
/*...CSS declarations here...*/
}
}
@include block(nav) { // <--- BAD: Attempt to reassign the nav block styles
/*...CSS declarations here...*/
}
Error: in `element': Attempt to reassign `.nav__item`
Error: in `block': Attempt to reassign `.nav`
The Order of Block Levels Matters
Note that the order of your block levels is important. For example, the following will cause an error:
/* Menu component */
@include block(menu, "component") { // <--- BAD: `component` is ahead of `object`
/*...styles are here...*/
}
/* Media object */
@include block(media, "object") {
/*...styles are here...*/
}
Error: the `object` level block `media` should not be behind of any `components` level blocks. Your block levels are: object, component
See Also
- https://en.bem.info/
- https://css-tricks.com/snippets/sass/bem-mixins/
- http://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/
- https://speakerdeck.com/dafed/managing-css-projects-with-itcss