In this tutorial we’re going to learn how to use PostCSS to make development of BEM/SUIT style CSS easier and more efficient.
These two methodologies lay out a naming convention for classes that makes it easier to keep your styles tightly function-oriented, and helps other developers recognize the purpose of various classes just from the way they’re named.
BEM was the forerunner of this type of class naming methodology, created by Yandex. The SUIT methodology is an approach based on BEM, but with some adjustments and additions made by Nicholas Gallagher. SUIT does everything BEM does, but to many users it is considered an enhancement.
Working with these methods definitely helps to produce better, more well structured CSS. However, the tricky part is that it can become tiresome manually typing out the class names required in this structure, and keeping track of how classes inter-relate can become a bit of a headache.
The postcss-bem plugin by Malte-Maurice Dreyer alleviates these issues through a combination of shortcuts and nesting, which you will learn to use as we move through this tutorial.
But first, let’s have a quick primer on the BEM and SUIT methods, to make sure you have a clear picture of the benefits of using the postcss-bem plugin, and of the way it’s used.
Quick Primer on BEM
Block
In BEM blocks are high-level chunks of a design; the building blocks the site is made from. A block should be a piece of your site that’s independent of other pieces, and could theoretically be placed anywhere in your layout, even nested inside another block.
For example, search form “blocks” on your site might use the class .search-form
.
Element
An element in BEM is a subsection inside a block. They are signified by appending a two underscore __
separator and an element name to the parent block name.
For example, a search form might include heading, text field and submit button elements. Their class names might be .search-form__heading
, .search-form__text-field
and .search-form__submit-button
respectively.
Modifier
A modifier is applied to a block or element to signify a change in its presentation, or a change in its state. They are signified by appending a separator and a modifier name to the block or element in question.
The official BEM site docs state that modifier separators should be a single underscore _
. However the "BEM-like" convention of CSS Guidelines by Harry Roberts employs two dashes --
and is probably more widely used and known than the official BEM convention.
For example, in a design you may wish to present advanced search forms differently to regular search forms, and hence create the modifier class .search-form_advanced
(official BEM) or .search-form--advanced
(BEM-like).
In another example, you might want to change the form’s appearance due to a change in state, such as if invalid content has just been submitted, and hence create the modifier .search-form_invalid
(official BEM) or .search-form--invalid
(BEM-like).
Quick Primer on SUIT
SUIT comprises Utilities and Components. Within components there can be Modifiers, Descendants and States.
SUIT uses a combination of pascal case (PascalCase), camel case (camelCase) and dashes. Its conventions enforce a limit on the sometimes confusing number of dashes and underscores that can appear in BEM. For example, the BEM class .search-form__text-field
would be .SearchForm-textField
in SUIT.
Utility
Utilities handle structure and positional styling, and are written in such a way that they can be applied anywhere in a component. They are prefixed with u-
and written in camel case. For example, .u-clearFix
.
Component
A component in SUIT takes the place of a block in BEM. Components are always written in pascal case and are only part of SUIT that uses pascal case, making them easy to identify. For example, .SearchForm
.
Component Namespace
Components can optionally be prefixed with a namespace and single dash nmsp-
to ensure conflicts are prevented, e.g. .mine-SearchForm
.
Descendent
A descendent in SUIT replaces an element in BEM. It uses a single dash -
and is written in camel case. For example .SearchForm-heading
, .SearchForm-textField
and .SearchForm-submitButto
.
Modifier
SUIT uses modifiers as does BEM, however their role is more tightly controlled. A SUIT modifier is generally only applied directly to a component, not to a descendent. It should also not be used to represent state changes, as SUIT has a dedicated naming convention for states.
Modifiers are written in camel case and are preceded by two dashes --
. For example, .SearchForm--advanced
.
State
State classes can be used to reflect changes to a component’s state. This allows them to be clearly differentiated from modifiers, which reflect modification of a component’s base appearance regardless of state. If necessary, a state can also be applied to a descendent.
States are prefixed with is-
and are written in camel case. They are also always written as adjoining classes. For example .SearchForm.is-invalid
.
Setup Your Project
Now that you have the essentials of BEM and SUIT down, it’s time to setup your project.
You’ll need an empty project using either Gulp or Grunt, depending on your preference. If you don’t already have a preference for one or the other I recommend using Gulp as you’ll need less code to achieve the same ends, so you should find it a bit simpler to work with.
You can read about how to setup Gulp or Grunt projects for PostCSS in the previous tutorials
respectively.
If you don't want to manually setup your project from scratch though, you can download the source files attached to this tutorial, and extract either the provided Gulp or Grunt starter project into an empty project folder. Then with a terminal or command prompt pointed at the folder run the command npm install
.
Install Plugins
Next, you’ll need to install the postcss-bem plugin. We'll also be installing a plugin that can work in with it quite well: postcss-nested.
Whether you’re using Gulp or Grunt, run the following command inside your project folder:
npm install postcss-bem postcss-nested --save-dev
Now we’re ready to load the plugins into your project.
Load Plugins via Gulp
If you’re using Gulp, add these variables under the variables already in the file:
var bem = require('postcss-bem'); var nested = require('postcss-nested');
Now add each of those new variable names into your processors
array:
var processors = [ bem, nested ];
Do a quick test that everything is working by running the command gulp css
then checking that a new “style.css” file has appeared in your project’s “dest” folder.
Load Plugins via Grunt
If you’re using Grunt, update the processors
object, which is nested under the options
object, to the following:
processors: [ require('postcss-bem')(), require('postcss-nested')() ]
Do a quick test that everything is working by running the command grunt postcss
then checking that a new “style.css” file has appeared in your project’s “dest” folder.
Okay, you’re ready to go. Let’s learn how to generate BEM and SUIT structure.
BEM and SUIT with postcss-bem
There can be some unwieldiness developing in BEM or SUIT structure when writing code out manually, as continually repeating the same identifiers in class names can become tiresome, and keeping track of which elements and descendents belong to which blocks and components can get confusing.
When you use postcss-bem however, it becomes easy to make sense of the structure of your code at a glance, and repetition in typing out class names becomes virtually non-existent.
Generating SUIT Structure
Despite its name, by default postcss-bem will output according to SUIT syntax rather than BEM. You can output in BEM syntax, which we will cover later, but the plugin is primarily designed to output SUIT, so for that reason, we’ll start with SUIT syntax.
Generating a Component
To create a component, use the syntax @component ComponentName {...}
.
Try this out by adding a SearchForm
component to your “src/style.css” file:
@component SearchForm { padding: 0; margin: 0; }
Compile it and your resulting code should be:
.SearchForm { padding: 0; margin: 0; }
Generating a Descendent
To create a descendent, use the syntax @descendent descName {...}
nested inside the parent component.
Add a descendent named textField
inside your SearchForm
component like so:
@component SearchForm { padding: 0; margin: 0; /* Nest descendent under component */ @descendent textField { border: 1px solid #ccc; } }
After compiling, you should now see:
.SearchForm { padding: 0; margin: 0; } .SearchForm-textField { border: 1px solid #ccc; }
Generating a Modifier
Create a modifier to a component with the syntax @modifier name {...}
, nested inside the component it effects. Modifiers should typically be placed at the top of your component, above any descendents and states.
Add a modifier named advanced
to your SearchForm
component with the following code:
@component SearchForm { padding: 0; margin: 0; /* Typically, place modifiers above descendents */ @modifier advanced { padding: 1rem; } @descendent textField { border: 1px solid #ccc; } }
Recompile your code and you should see your new advanced
component modifier:
.SearchForm { padding: 0; margin: 0; } .SearchForm--advanced { padding: 1rem; } .SearchForm-textField { border: 1px solid #ccc; }
Generating a State
States are created via the syntax @when name {...}
and can be nested inside a component or a descendent.
Add a state named invalid
to your textField
descendent using this code:
@component SearchForm { padding: 0; margin: 0; @modifier advanced { padding: 1rem; } @descendent textField { border: 1px solid #ccc; /* This creates a state for the textField descendant */ @when invalid { border: 1px solid red; } } }
Now when you compile your code you’ll see it contains your new invalid
state:
.SearchForm { padding: 0; margin: 0; } .SearchForm--advanced { padding: 1rem; } .SearchForm-textField { border: 1px solid #ccc; } .SearchForm-textField.is-invalid { border: 1px solid red; }
Namespacing Components
You can namespace your components, and all the descendents, modifiers and states nested within them, by surrounding them with @component-namespace name {...}
. You can, if you like, wrap your entire stylesheet with this namespace so all your classes are automatically prefixed with it.
Try this out by wrapping all your code so far with @component-namespace mine {...}
:
@component-namespace mine { @component SearchForm { padding: 0; margin: 0; @modifier advanced { padding: 1rem; } @descendent textField { border: 1px solid #ccc; @when invalid { border: 1px solid red; } } } }
After compiling, you’ll see that now every one of your components is prefixed with mine-
:
.mine-SearchForm { padding: 0; margin: 0; } .mine-SearchForm--advanced { padding: 1rem; } .mine-SearchForm-textField { border: 1px solid #ccc; } .mine-SearchForm-textField.is-invalid { border: 1px solid red; }
Generating a Utility
Utilities are created with the syntax @utility utilityName {...}
. You’ll recall that when setting up your project, you installed the postcss-nested plugin. We did this as it can be very handy to use in unison with postcss-bem, as you’ll see in this example where we create a clearFix
utility:
@utility clearFix { &:before, &:after { content: ""; display: table; } &:after { clear: both; } /* If supporting IE 6/7 */ *zoom: 1; }
After adding the above code, compile and you’ll see this new utility has been created:
.u-clearFix { /* If supporting IE 6/7 */ zoom: 1; } .u-clearFix:before, .u-clearFix:after { content: ""; display: table; } .u-clearFix:after { clear: both; }
Generating BEM Structure
To activate BEM syntax output in postcss-bem, pass the option style: 'bem'
in your Gulpfile or Gruntfile like so:
/* Gulpfile */ var processors = [ bem({style: 'bem'}), nested ]; /* Gruntfile */ processors: [ require('postcss-bem')({style: 'bem'}), require('postcss-nested')() ]
By default postcss-bem will use the official separator for a modifier of a single underscore _
. If it’s important for your project that you use the more common separator of two dashes --
instead, you can change the config for the postcss-bem plugin by going to the node_modules/postcss-bem folder of your project, opening up index.js, locating line 15 and changing this:
bem: { separators: { namespace: '--', descendent: '__', modifier: '_' } }
...to this:
bem: { separators: { namespace: '_', descendent: '__', modifier: '--' } }
Generating a Block
Because a “block” in BEM correlates with a “component” in SUIT, use the syntax @component block-name {...}
to generate a block.
To create a search-form
block add this code:
@component search-form { padding: 0; margin: 0; }
Then compile and you should see:
.search-form { padding: 0; margin: 0; }
Generating a Element
As an “element” in BEM correlates to a “descendent” in SUIT, they can be created with the syntax @descendent element-name {...}
nested inside the parent block.
To create a text-field
element add the following:
@component search-form { padding: 0; margin: 0; @descendent text-field { border: 1px solid #ccc; } }
On compilation, you’ll see your new element has been created:
.search-form { padding: 0; margin: 0; } .search-form__text-field { border: 1px solid #ccc; }
Generating a Modifier
Even though BEM allows modifiers to both blocks and elements, the postcss-bem plugin will only process them if nested inside blocks and not elements, due to the SUIT convention of modifiers being applied to components not descendents. They can be created with the syntax @modifier name {...}
, nested inside its parent block.
Add an advanced
modifier to your search-form
component like so:
@component search-form { padding: 0; margin: 0; @modifier advanced { padding: 1rem; } @descendent text-field { border: 1px solid #ccc; } }
And on compilation it will yield:
.search-form { padding: 0; margin: 0; } .search-form_advanced { padding: 1rem; } .search-form__text-field { border: 1px solid #ccc; }
No Utilities or States, but Namespaces Are In
While in BEM mode the @utility
and @when
syntaxes will not compile into anything, given BEM does not use utilities or states.
However, even though it’s not generally part of BEM, the @component-namespace
syntax will still work if you wish to use it in your BEM stylesheet. It will prefix your classes with name--
:
.mine--search-form { padding: 0; margin: 0; } .mine--search-form_advanced { padding: 1rem; } .mine--search-form__text-field { border: 1px solid #ccc; }
Let’s Recap
Now you know all about how to shortcut your BEM and SUIT development, and make the overall process more easier. Let’s summarize everything we’ve covered:
- BEM and SUIT are class naming conventions that help to keep stylesheets function oriented and organized, as well as helping other developers recognize the purpose of various classes.
- SUIT is like BEM, but with some extras added and adjustments made
- The postcss-bem plugin provides shortcuts for creating BEM and SUIT classes, such as
@component
,@descendent
,@modifier
etc. - The plugin also allows code to be nested in a helpful way, e.g. modifiers are nested inside the component or block they modify.
- Namespacing can be done automatically by wrapping classes with
@component-namespace name {...}
In the Next Tutorial
Coming up next we look at another great way to take advantage of PostCSS, and that is by putting together a toolkit of shorthand and shortcuts we can take to make our coding faster and more efficient.
I’ll see you there!
by Kezz Bracey via Tuts+ Code
No comments:
Post a Comment