This article is the sixth in our new series that introduces the latest, useful and freely available tools and techniques, developed and released by active members of the Web design community. The first article covered PrefixFree; the second introduced Foundation, a responsive framework; the third presented Sisyphus.js, a library for Gmail-like client-side drafts, the fourth shared with us a free plugin called GuideGuide and the fifth presented Erskine Design's responsive grid generator Gridpak. Today, we are happy to feature a toolkit devised by Yandex: BEM.
BEM stands for "Block", "Element", "Modifier". It is a front-end methodology: a new way of thinking when developing Web interfaces. This article will elaborate on the theory as well as the practice of building websites at Yandex—one of the leading internet companies in Russia.
Due to the length of this article, it was split into three parts:
- Part 1: BEM Principles
- Part 2: Blocks Reiteration
- Part 3: File System Representation For A Block
BEM Principles
To begin, let's first put BEM in some historical perspective.
We first began sketching out the internal front-end framework at Yandex around the year 2007, starting with a robust CSS naming convention, and a file system layout that was associated with it. Since the naming convention was well-structured, it seemed suitable to develop certain JavaScript helpers (to work with the DOM and CSS classes in particular, on a higher level of abstraction). We then used those approaches to build an internal library of UI components that could be shared among our various websites and rich applications, built using different technology stacks (XML/XSLT, Python/Django, Perl/TT2).
As our ambitions, complexity and performance requirements grew, we aimed at replacing XSLT and Perl templates with a JS-based declarative templating DSL, built on top of Node.js. Along with those efforts, we looked into simplifying development workflow and developed a bunch of command-line tools that already helped us manage front-end code on the file system, preprocess CSS and JavaScript code, and so on, and so forth.
Some parts of the BEM stack started as open source projects, while others (like the UI component library) are being gradually open sourced. Our goal is to publish most of them during 2012.
BEM is a toolkit that will help address and resolve front-end issues quickly and effectively. It is available in a range of reusable code libraries—all of them are hosted on Github and are completely open source.
BEM Principles
One of the most common examples of a methodology in programming is Object-Oriented Programming. It's a programming paradigm embodied by many languages. In some ways, BEM is similar to OOP—a way of describing reality in code, with a range of patterns, and a way of thinking about program entities regardless of the programming languages being used.
We've used BEM principles to create a set of front-end development techniques and tools that allow us to build websites quickly and maintain them over a long period of time. The principles are the following:
Unified Data Domain
Imagine an ordinary website, like the one pictured below:
While developing such a website, it's useful to mark out "blocks" from which the website consists of. For example, in this picture there are Head
, Main Layout
and Foot
blocks. The Head
in turn consists of Logo
, Search
, Auth Block
and Menu
. Main Layout
contains a Page Title
and a Text Block
:
Giving each part of the page a name is very useful when it comes to team communication.
A project manager could ask:
- To make the
Head
bigger, or - To create a page without a
Search
form in theHead
.
An HTML guy could ask a fellow JavaScript developer:
- To make
Auth Block
animated, etc.
Let's now take a closer look at what constitutes BEM:
Block
A block
is an independent entity, a "building block" of an application. A block can be either simple or compound (containing other blocks).
Example Search form block:
Element
An element
is a part of a block that performs a certain function. Elements are context-dependent: they only make sense in the context of the block that they belong to.
Example
An input field and a button are elements of the Search Block:
Means Of Describing Pages And Templates
Blocks and elements constitute page content. Besides simply being present on a page, their arrangement is also important.
Blocks (or elements) may follow each other in a certain order. For example, a list of goods on a commerce website:
...or menu items:
Blocks may also be contained inside other blocks. For example, a Head Block
includes other blocks:
Besides, our building blocks need a way to describe page layout in plain text. To do so, every block and element should have a keyword that identifies it.
A keyword designating a specific block is called Block Name
. For example, Menu
can be a keyword for the Menu Block
and Head
can be a keyword for the Head
block.
A keyword designating an element is called Element Name
. For example, each item in a menu is an element Item
of the Menu
block.
Block names must be unique within a project to unequivocally designate which block is being described. Only instances of the same block can have the same names. In this case, we can say that one block is present on the page twice (or 3, 4, times... etc.).
Element names must be unique within the scope of a block. An element can be repeated several times. For example, menu items:
Keywords should be put in a certain order. Any data format that supports nesting (XML, JSON) will do:
<b:page>
<b:head>
<b:menu>
...
</b:menu>
<e:column>
<b:logo/>
</e:column>
<e:column>
<b:search>
<e:input/>
<e:button>Search</e:button>
</b:search>
</e:column>
<e:column>
<b:auth>
...
</b:auth>
<e:column>
</b:head>
</b:page>
In this example, b
and e
namespaces separate block nodes from element nodes.
The same in JSON:
{
block: 'page',
content: {
block: 'head',
content: [
{ block: 'menu', content: ... },
{
elem: 'column',
content: { block: 'logo' }
},
{
elem: 'column',
content: [
{
block: 'search',
content: [
{ elem: 'input' },
{
elem: 'button',
content: 'Search'
}
]
}
]
},
{
elem: 'column',
content: {
block: 'auth',
content: ...
}
}
]
}
}
Examples above show an object model with blocks and elements nested inside each other. This structure can also contain any number of custom data fields. We call this structure BEM Tree
(by analogy with DOM tree).
Final browser markup is generated by applying template transformations (using XSL or JavaScript) to a BEM tree.
If a developer needs to move a block to a different place on a page, he does so by changing the BEM tree. Templates generate the final view themselves.
In our recent products we went with JSON as a page description format. It is then turned into HTML by a JS-based template engine. The tools we use are listed at the end of this article.
Block Independence
As projects grow, blocks tend to be added, removed, or moved around on the page. For example, you may want to swap the Logo
with the Auth Block
, or place the Menu
under the Search Block
.
To make this process easier, blocks must be Independent
.
An Independent
block is implemented in a way that allows arbitrary placement anywhere on the page—including nesting inside another block.
Independent CSS
From the CSS point of view it means that:
- A block (or an element) must have a unique "name" (a CSS class) that could be used in a CSS rule.
- HTML elements must not be used in CSS selectors (.menu td) as such selectors are inherently not context-free.
- Cascading selectors for several blocks should be avoided.
Naming for Independent CSS Classes
One of the possible naming schemes for CSS classes that satisfies said requirements is the following:
- CSS class for a block coincides with its
Block Name
.
<ul class="menu">
...
</ul>
- CSS class for an element is a
Block Name
and anElement Name
separated by some character(s)
<ul class="menu">
<li class="menu__item">
...
</li>
<li class="menu__item">
...
</li>
</ul>
It's necessary to include block name in a CSS class for an element to minimize cascading. It's also important to use separators consistently to allow the tools and helpers to have unambiguous programmatic access to the elements.
Different naming schemes can be used. Take a look here for the naming convention we used.
Independent Templates
From the template engine's perspective, block independence means that:
- Blocks and elements must be described in the input data.
Blocks (or elements) must have unique "names" to make things like "
Menu
should be placed here" expressible in our templates. - Blocks may appear anywhere in a BEM tree.
Independent templates for blocks
When coming across a block in a template, the template engine should be able to unambiguously transform it into HTML. Thus, every block should have a template for that.
For example, a template can look like this in XSL:
<xsl:template match="b:menu">
<ul class="menu">
<xsl:apply-templates/>
</ul>
</xsl:template>
<xsl:template match="b:menu/e:item">
<li class="menu__item">
<xsl:apply-templates/>
</li>
<xsl:template>
We are gradually discarding XSLT in our products in favor of our own JavaScript-based template engine XJST. This template engine absorbs everything we like about XSLT (we are fans of declarative programming), and implements it with JavaScript's productivity on either the client or the server side.
We, at Yandex, write our templates using a domain-specific language called BEMHTML, which is based on XJST. The main ideas of BEMHTML are published in the BEM club on Ya.Ru (in Russian).
Smashing Special: A Three-Part Article
Due to the length of the article, it was split into three parts:- Part 1: BEM Principles
- Part 2: Blocks Reiteration
- Part 3: File System Representation For A Block
(jvb)