Case StudyThe Evolution Of The BEM Methodology

Advertisement

This article is a case study about the evolution of BEM1, a methodology that enables team members to collaborate and communicate ideas using a unified language that consists of simple yet powerful terms: blocks, elements, modifiers. Learn about the challenges that a big company faces when gradually building an entire ecosystem of services with an ever-growing team of developers.

Once upon a time, in a distant country far far away, an IT company named Yandex started developing Web search and related services. Time went by and its services grew, and more and more front-end developers put tireless effort into improving the ecosystem of Yandex. Great things they did, and amazing tools they built, making their developers’ lives easier, and the time has now come to share that knowledge with the community, to unleash the magic power of open source for the benefit of all good people out there.

Front-end developers are well known for their insatiable curiosity, which often yields innovation, as well as their remarkable laziness, which drives them to devise sophisticated systems in order to save precious time and to unify and automate everything.

Let’s travel back in time to 2005 and sneak a peek over the shoulder of a very very busy Yandex front-end developer and thus see…

…Where It All Began

Back in 2005, the focus was still pretty much on the server side of things. From a front-ender’s perspective, a typical Yandex project was a set of static HTML pages used as a base reference to build advanced templates such as XSL style sheets. These pages were kept in a separate folder that looked like this after a checkout:

about.html
index.html
…
project.css
project.js
i/
    yandex.png

There was a static HTML file for each page, with all of the CSS pushed into a single style sheet, project.css, and all JavaScript placed in a single project.js file, with both files shared between all project pages. Back in 2005, JavaScript was only sparsely applied, so all of the interaction magic could fit comfortably in a small file. Images resided in a separate folder, because they were numerous. With IE 5 roaming in the wild and no CSS3, images were used for all sorts of eye candy, even for rounded corners (none of you younger Web developers would probably believe me).

To retain the basic structure, style definitions for different page sections were separated using plain CSS comments:

/* Content container (begin) */
    #body
        {
            font: 0.8em Arial, sans-serif;

            margin: 0.5em 1.95% 0.5em 2%;
        }
/* Content container (end) */

/* Graphical banner (begin) */
    .banner
        {
            text-align: center;
        }

    .banner a
        {
            text-decoration: none;
        }
/* Graphical banner (end) */

Both IDs and class names were used in HTML markup.

Bits of HTML were manually pasted into production XSL style sheets, and all changes were synced two-way, manually. That was hard, and when it wasn’t hard, it was dull.

Mid-Scale Projects

By the beginning of 2006, the first version of Yandex.Music had been under heavy development. Multiple pages, each unlike the others, didn’t fit well into familiar simplistic concepts. Dozens of CSS classes that one had to invent meaningful names for, a growing number of unintentional dependencies spreading across the project — all of this was calling for a better solution.

Here is a typical piece of CSS code from those days:

/* Albums (begin) */
        .result .albums .info
            {
             padding-right: 8.5em;
            }

        .result .albums .title
            {
             float: left;

             padding-bottom: 0.3em;
            }

        .result .albums .album .listen
            {
             float: left;

             padding: 0.3em 1em 0 1em;
            }

        .result .albums .album .buy
            {
             float: left;

             padding: 0.4em 1em 0 1.6em;
            }

        .result .albums .info i
            {
             font-size: 85%;
            }
    /* Albums (end) */

Long cascading rules were used throughout the code.

Have a look at another:

/* Background images (begin) */
        .b-foot div
            {
             height: 71px;

             background: transparent url(../i/foot-1.png) 4% 50% no-repeat;
            }

        .b-foot div div
            {
             background-position: 21%;
             background-image: url(../i/foot-2.png);
            }

        .b-foot div div div
            {
             background-position: 38%;
             background-image: url(../i/foot-3.png);
            }

        .b-foot div div div div
            {
             background-position: 54%;
             background-image: url(../i/foot-4.png);
            }

        .b-foot div div div div div
            {
             background-position: 71%;
             background-image: url(../i/foot-5.png);
            }

        .b-foot div div div div div div
            {
             background-position: 87%;
             background-image: url(../i/foot-6.png);
            }
    /* Background images (end) */

Notice that ID and tag name selectors were used in many rules.

At the same time, an even bigger project was being started wow.ya.ru2: a blogging platform, a place for people to interact, to share, to read and to engage.

There were dozens of various pages to support. And with the old-fashioned approach, the code was losing control on so many levels.

Blocks to the Rescue

We needed to specify a data domain to manage page interface objects. This was a methodology issue: we needed to clarify the way we worked with concepts such as classes, tags, visual components, etc.

For a typical Web page in a Yandex project, the HTML structure and its CSS styles were still the focus of our development efforts, with JavaScript being a supplementary technology. To be able to more easily maintain the HTML and CSS of many components, a new term was devised: “block.” A block was a part of a page design or layout whose specific and unique meaning was defined either semantically or visually.

In most cases, any distinct page element (either complex or simple) could be considered a block. Its HTML container got a unique CSS class, which also became a block name.

CSS classes for blocks got prefixes (b-, c-, g-) to provide a sort of namespace emulation in CSS. The naming convention itself was changed later, but here is the initial list, annotated:

  • b- (block)
    An independent block, placed on a page wherever you needed it.
  • с- (control)
    A control (i.e. an independent block), with a JavaScript object bound to it.
  • g- (global)
    A global definition, used sparingly and always defined for a specific, unique purpose. The number of these definitions were kept to a minimum.

Some suffixes were employed as well, such as:

  • -nojs (no JavaScript)
    A style rule to be applied with JavaScript turned off. An onload callback could remove these suffixes from all DOM nodes, semantically marking them up as “JavaScript-enabled.”

What’s Inside?

In an HTML container holding a block, some of the inner nodes had distinct CSS classes. This not only facilitated the creation of tag name-independent style rules, but also assigned semantically meaningful roles to each node. Such nodes were “block elements,” or simply “elements.”

The core distinction between a block and an element is an element’s inability to exist outside of its parent block’s context. If something couldn’t be detached from a block, it was an element; detachable elements (probably) should themselves be blocks.

At first, an element could exist only in a block container. Later, a technique was devised to place some elements outside and still keep the block consistent.

In style sheets, elements with a lot of CSS got extra indentation and were wrapped in comments:

/* Head (begin) */
        .b-head { … }

        /* Logo (begin) */
            .b-head .logo { … }
            .b-head .logo a { … }
        /* Logo (end) */

        /* Right side (begin) */
        .b-head .right { … }

            /* Info (begin) */
             .b-head .info { … }
             .b-head .info .exit a { … }
            /* Info (end) */

            /* Search (begin) */
             .b-head .search { … }
             .b-head .search div div, .b-head .search div div i { … }
            /* Search (end) */
        /* Right side (end) */
    /* Head (end) */

The Project’s File Structure Evolves

At Yandex, a front-end developer usually supports more than one project. Switching between different repositories and various branches is easier when all projects use the same (or a similar) file structure. Granularity is another requirement because it provides more flexibility for version-control systems and helps to avoid conflicts during concurrent development.

This led us to a more unified structure: CSS, JavaScript and image files would reside in separate folders. In CSS, there were dedicated files for IE-specific workarounds, to keep the main code clean and standards-compliant. In production, IE would get its well-earned CSS hackery via IE-only conditional comments.

JavaScript was being employed more and more; thus, the addition of optional components and libraries.

Here is a typical file structure:

index.html
css/
    yaru.css
    yaru-ie.css
js/
    yaru.js
i/
    yandex.png

IE-specific hacks could have gone into the main CSS file (yaru.css) if they complied with CSS standards:

/* Common definitions (begin) */
        body
            {
             font-family: Arial, sans-serif;
             font-size: 0.8em;

             padding: 0 0 2em 0;
             background: #fff;
            }

        * html body
            {
             font-size: 80%;
            }

Non-valid workarounds were put in a standalone yaru-ie.css file (loaded with IE-only conditional comments).

/* Common blocks (begin) */
        /* Artist (begin) */
            .b-artist .i i
             {
              top: expression(7 + (90 - this.parentNode.getElementsByTagName('img')[0].height)/2);
              filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../i/sticker-lt.png', sizingMethod='crop');
             }

Building A Framework: The Beginning

Designing similar projects eventually meant recreating the same blocks over and over again. Yandex is a portal and offers more than a hundred services that share the same corporate style, so careless copying and pasting wouldn’t work on that scale. Just to have something to begin with, we made a small compilation of reusable components, known internally as the common blocks library, or simply the common.

The first page fragments to be unified were the header, footer and some CSS typographic elements. Corresponding files were hosted on an internal dedicated server (common.cloudkill.yandex.ru in the listing below). Those were the early days of our unified framework.

Styles could be imported directly from that server:

@import url(http://common.cloudkill.yandex.ru/css/global.css);
@import url(http://common.cloudkill.yandex.ru/css/head/common.css);
@import url(http://common.cloudkill.yandex.ru/css/static-text.css);
@import url(http://common.cloudkill.yandex.ru/css/foot/common-absolute.css);
@import url(http://common.cloudkill.yandex.ru/css/foot/common-absolute-4-columns.css);
@import url(http://common.cloudkill.yandex.ru/css/list/hlist.css);
@import url(http://common.cloudkill.yandex.ru/css/list/hlist-middot.css);
@import url(http://common.cloudkill.yandex.ru/css/dropdown/dropdown.css);
@import url(http://common.cloudkill.yandex.ru/css/dropdown/dropdown-arrow.css);
@import url(slider.css);

/* Header (begin) */
    /* Service (begin) */
        .b-head .service h1 { … }
        .b-head .service h1, .b-head .service h1 a, .b-head .service h1 b { … }

Obviously, these were too many imports! So, we decided to precompile styles (and, later, JavaScript files) before deployment. The compilation would replace @import directives with the file’s actual contents (a process called “inlining”) and would perform optimizations. Our internal inlining tool evolved from a simple wrapper script into an open-source project, Borschik3. Try it out!

Independent Blocks As A Concept

By the fall of 2007, our everyday practice had gotten some theory behind it. The concept of independent blocks, the basic idea behind our understanding of HTML and CSS layouts, was featured at the ClientSide 2007 conference in Moscow, Russia.

In that presentation, the first attempt to define a block was made.

Blocks: Declaration of Independence

In our attempt to produce a formal (in fact, semi-formal) definition of a block, the following three principles were highlighted:

  1. Only class names (not IDs) should be used for CSS.
  2. Each block’s class name should have a namespace (prefix).
  3. Every CSS rule must belong to a block.

As soon as unique IDs were dropped, a block could be used on the same page more than once. This also allowed two or more classes to coexist in the same DOM node, which turned out to be quite useful later.

Simple and Compound Blocks: The Misclassification

We defined “simple” blocks as ones not able to hold other blocks anywhere inside. “Compound” blocks, on the other hand, were allowed (even required) to have nested blocks.

This classification was naive. Even the simplest blocks were sometimes wrapped around other blocks and had to be “upgraded” and refactored to fit the new role. This misclassification backfired so many times, in fact, that we finally accepted the opposite principle: any block should allow for arbitrary content to be embedded, whenever possible.

Completely Independent Blocks

CSS definitions weren’t bulletproof when we mixed a lot of styled content originating from different sources on a single page. In complex layouts, blocks could alter each other’s appearance because of conflicts in element names. Tag name-based CSS rules might match more nodes than intended. Therefore, a stricter version of an independent block (named “completely independent block,” or CIB) was defined, with the following rules added:

  1. Never match CSS with tag names. Use class names for everything.
    .b-user b → .b-user .first-letter
  2. Class names for block elements must be prefixed with the parent’s block name.
    .b-user .first-letter → .b-user-first_letter

Such class names tend to be much longer, and the resulting HTML code was considerably bigger.

This was the main reason why CIB was considered to be a costly solution, used more as a remedy than as an everyday practice.

Prefixes

As you are certainly aware, naming variables is one of the most difficult development problems, ever. We approached it cautiously and came up with four prefixes that would be allowed in block names, each with its own semantics.

  • b-
    Common blocks
  • h-
    Holsters, used to glue several elements together
  • l-
    Layout grids
  • g-
    Global styles

Modifiers

A “modifier” can be defined as a particular state of a block, a flag holding a specific property.

This is best explained with an example. A block representing a button could have three default sizes: small, normal and big. Instead of creating three different blocks, you would assign a modifier to the block. The modifier would require a name (for example, size) and a value (small, normal or big).

There are two reasons for a block to change its presentation state:

  1. A block’s presentation could be altered because of its placement in the layout. This was called a “context-dependent” modification.
  2. An additional (postfixed) class name could change a block’s appearance by applying extra CSS rules. This was a “context-independent” modifier.
    class="b-block b-block-postfix"

A Unified Portal-Wide Framework

At the beginning of 2008, Yandex was going through a major review of its internal design policies. We decided to create a branding book (for internal use) to enforce best practices in interface design, company-wide.

This task was assigned to the front-end team, and after some pondering of options, we decided to proceed with it using familiar technologies: HTML and CSS.

Interfaces evolve fast, so fast that any long-term attempt to describe interfaces with words and pictures would become obsolete even before completion. We needed a branding book that would represent our interfaces as they were: changing rapidly yet still unified between different Yandex services and products.

Therefore, we decided that our interface branding book should be built with the same blocks that we used to build our websites. Blocks could be shared between projects and would represent the latest in Yandex’s interface design.

We decided to build a portal-wide framework of blocks so that all could benefit from it and contribute back. The project was internally named “Lego.”

Framework Repository Structure: First Approach

The top-most level corresponded to various available implementations:

css/
html/
js/
xml/
xsl/

Each implementation had its own folder sub-structure.

CSS went into three different folders:

css/
    block/
        b-dropdown/
            b-dropdown.css
    service/
        auto/
            block/
                b-head-logo-auto.css
            head.css
    util/
        b-hmenu/
            b-hmenu.css

  1. block
    These were blocks shared between services.
  2. util
    There were general-purpose blocks ready to be open-sourced.
  3. service
    These were CSS styles for specific Yandex services, used for branding, headers and footers, etc.

The HTML’s folder structure was identical to the CSS’:

html/
    block/
        b-dropdown.html
    service/
        auto/
            l-head.html
    util/
        b-hmenu.html

JavaScript was loosely structured and used inconsistently between services, though:

js/
    check-is-frame.js
    check-session.js
    clean-on-focus.js
    dropdown.js
    event.add.js
    event.del.js

Each service had a corresponding XML file that semantically described its page header (and that provided necessary project-specific data). In conjunction with an XSL style sheet, the XML file was enough to generate the header HTML code.

xml/
    block/
        b-head-tabs-communication.xml
        common-services.ru.xml
        head-messages.ru.xml
    service/
        auto/
            head.xml

XSL templates for various blocks (one file per block) were contained in one folder:

xsl/
    block/
        b-dropdown.xsl
        b-head-line.xsl
        i-common.xsl
        i-locale.xsl
        l-foot.xsl
        l-head.xsl

What about integration?

Lego was linked to projects with the help of a version-control feature known as svn:externals.

When a package was built for production deployment, the code from the external library (Lego) was embedded in the package, similar to static library linking in compiled languages.

Lego provided an SVN branch for each of its major releases. Sticking to a branch in svn:externals allowed for hot fixes to be introduced to a project; for extreme stability, a project could be frozen at a specific Lego revision. In either case, major version switches could be prepared and done whenever necessary.

This simple technique proved quite flexible, and it is employed to this day for many Yandex services.

Per-Page Files

CSS files imported rule definitions for blocks used on a page from the Lego folder structure.

@import url(../../block/l-head/l-head.css);
@import url(../../block/b-head-logo/b-head-logo.css);
@import url(../../block/b-head-logo/b-head-logo_name.css);
@import url(block/b-head-logo-auto.css);

The consistency of importing directives was maintained manually.

By that point, we hadn’t yet come to a convention for unified file naming, and we tried several approaches.

Portal-Wide Framework: Lego 1.2 (2008)

Upon the release of Lego 1.2, the code had been refactored and the folder structure changed.

common/
    css/
    js/
    xml/
    xsl/
example/
    html/
service/
    auto/
        css/
        xml/

Blocks previously separated and placed in util and block folders were combined. Common styles shared by most blocks were moved to common/css. We had been pondering the possibility of open-sourcing the code but postponed it until two years later.

common/
    css/
        b-dropdown/
            arr/
             b-dropdown.arr.css
             b-dropdown.arr.ie.css
             b-dropdown.css
             b-dropdown.ie.css

IE-specific styles were renamed from *-ie.css to *.ie.css.

All contents of optional CSS files (such as b-dropdown_arr.css) were moved into separate folders (arr/b-dropdown.arr.css).

For class name-based modification of a block, the underscore was assigned as a separator, replacing the single dash that was used previously.

This made a block name visually separate from a modifier name, and it proved quite useful for us while developing automated tools because it allowed for unambiguous search and pattern matching.

BEM, Est. 2009

In March 2009, Lego 2.0 was released. That event marked the rise of the BEM methodology.

BEM stands for “block, element, modifier,” the three key entities we use to develop Web components.

sign_theme_stripe (1)4

Lego 2.0 in 2009

What key update did version 2.0 deliver?

It established the primacy of the “block” concept over underlying implementation technologies.

Each block was contained in a separate folder, and each technology (CSS, JavaScript, XSL, etc.) represented by a separate file. Documentation got its own file type, such as .wiki.

What other principles did we follow at the time?

Terminology Excerpts

An “independent block” could be used on any Web page and placed anywhere in the layout. Because we used XML and XSL templating, a block was represented by a node in the lego namespace.

XML:

<lego:l-head>
<lego:b-head-logo>

In HTML, a block container node got a class name corresponding exactly to the block’s name.

HTML:

<table class="l-head">
<div class="b-head-logo">

CSS:

.l-head
.b-head-logo

All block files (CSS, JavaScript, HTML, XSL) were stored in the block’s folder:

    common/
        block/
            b-head-logo/
             b-head-logo.css
             b-head-logo.xsl
             b-head-logo.js
             b-head-logo.wiki

In XML files that define page structure, blocks are defined with nodes in the lego namespace (with the block name’s prefix omitted):

<lego:b-head-logo>
    <lego:name/>
</lego:b-head-logo>

Prefixes for HTML classes inside the block were omitted as well.

<div class="b-head-logo">
    <span class="name">Web Service Name Here</span>
</div>

.b-head-logo .name { … }

Files describing block elements each got their own folder:

common/
    block/
        b-head-logo/
            name/
             b-head-logo.name.css
             b-head-logo.name.png
             b-head-logo.name.wiki

Modifiers in XML were specified as node attributes in the lego namespace:

<lego:b-head-tabs lego:theme="grey">

In HTML, an extra class name was added:

<div class="b-head-tabs b-head-tabs_grey">

.b-head-tabs_grey { … }

Modifier files (i.e. styles and so on) went into separate folders, prefixed with an underscore:

common/
    block/
        b-head-logo/
            _theme/
             b-head-logo_gray.css
             b-head-logo_gray.png
             b-head-logo_gray.wiki

Declarations in XML

All Lego components used in a project were defined in an XML file:

<lego:page>
    <lego:l-head>
        <lego:b-head-logo>
            <lego:name/>
        </lego:b-head-logo>

        <lego:b-head-tabs type="search-and-content"/>

This XML allowed for CSS imports to be generated:

@import url(../../common/block/global/_type/global_reset.css);
@import url(../../common/block/l-head/l-head.css);
@import url(../../common/block/b-head-logo/b-head-logo.css);
@import url(../../common/block/b-head-logo/name/b-head-logo.name.css);
@import url(../../common/block/b-head-tabs/b-head-tabs.css);
@import url(../../common/block/b-dropdown/b-dropdown.css);
@import url(../../common/block/b-dropdown/text/b-dropdown.text.css);
@import url(../../common/block/b-pseudo-link/b-pseudo-link.css);
@import url(../../common/block/b-dropdown/arrow/b-dropdown.arrow.css);
@import url(../../common/block/b-head-search/b-head-search.css);
@import url(../../common/block/b-head-search/arrow/b-head-search.arrow.css);
@import url(../../common/block/b-search/b-search.css);
@import url(../../common/block/b-search/input/b-search.input.css);
@import url(../../common/block/b-search/sample/b-search.sample.css);
@import url(../../common/block/b-search/precise/b-search.precise.css);
@import url(../../common/block/b-search/button/b-search.button.css);
@import url(../../common/block/b-head-userinfo/b-head-userinfo.css);
@import url(../../common/block/b-head-userinfo/user/b-head-userinfo.user.css);
@import url(../../common/block/b-user/b-user.css);
@import url(../../common/block/b-head-userinfo/service/b-head-userinfo.service.css);
@import url(../../common/block/b-head-userinfo/setup/b-head-userinfo.setup.css);
@import url(../../common/block/b-head-userinfo/region/b-head-userinfo.region.css);
@import url(block/b-head-logo/b-head-logo.css);
@import url(block/b-head-search/b-head-search.css);

This example shows that common styles were imported first; then, project styles applied extra definitions on top of that. This made project-specific changes possible, while maintaining a common shared code base.

The same XML declarations allowed for JavaScript includes to be autogenerated.

include("../../common/block/i-locale/i-locale.js");
include("../../common/block/b-dropdown/b-dropdown.js");
include("../../common/block/b-search/sample/b-search.sample.js");
include("../../common/block/b-head-userinfo/user/b-head-userinfo.user.js");

XSL template imports were autogenerated as well, using the same XML-based definitions:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:import href="../../common/block/i-common/i-common.xsl"/>
<xsl:import href="../../common/block/i-items/i-items.xsl"/>
<xsl:import href="../../common/block/l-head/l-head.xsl"/>
<xsl:import href="../../common/block/b-head-logo/b-head-logo.xsl"/>
<xsl:import href="../../common/block/b-head-logo/name/b-head-logo.name.xsl"/>
<xsl:import href="../../common/block/b-head-tabs/b-head-tabs.xsl"/>
<xsl:import href="../../common/block/b-dropdown/b-dropdown.xsl"/>
<xsl:import href="../../common/block/b-pseudo-link/b-pseudo-link.xsl"/>
<xsl:import href="../../common/block/b-head-search/b-head-search.xsl"/>
<xsl:import href="../../common/block/b-search/b-search.xsl"/>
<xsl:import href="../../common/block/b-search/input/b-search.input.xsl"/>
<xsl:import href="../../common/block/b-search/sample/b-search.sample.xsl"/>
<xsl:import href="../../common/block/b-search/precise/b-search.precise.xsl"/>
<xsl:import href="../../common/block/b-search/button/b-search.button.xsl"/>
<xsl:import href="../../common/block/b-head-userinfo/b-head-userinfo.xsl"/>
<xsl:import href="../../common/block/b-user/b-user.xsl"/>
<xsl:import href="../../common/block/b-head-userinfo/service/b-head-userinfo.service.xsl"/>
<xsl:import href="../../common/block/b-head-userinfo/setup/b-head-userinfo.setup.xsl"/>
<xsl:import href="../../common/block/b-head-userinfo/region/b-head-userinfo.region.xsl"/>

</xsl:stylesheet>

Code generation was an important step forward. From this point onward, we didn’t have to maintain dependencies manually.

CSS Selector Speed, Revisited (2009)

During the major redesign of the Yandex.Mail service in 2009, interface responsiveness and overall speed were the key goals. We wanted to release a Web application that felt as fast as desktop software, maybe even faster.

Client-side (i.e. in-browser) XSL transformations were employed as the main templating solution (the XML with all of the data was loaded separately). According to initial measurements, XSL transforms were applied almost instantly, but the resulting HTML code took significant time to be appended to the DOM. Disabling CSS, however, made that problem go away magically.

After studying various factors that could affect rendering speed, CSS selectors were identified as a major source of the slowdown. The bigger the DOM tree and CSS style sheet, the longer it took for all CSS rules to be applied.

A summary of our study is available5 (in Russian).

It turns out that switching to simple selectors and eliminating CSS cascades wherever possible enabled the CSS rules to be applied much faster. Selectors based on a single class name were quick, and browsers handled them with ease. We already had a solution that could use such selectors, the so-called “completely independent blocks” (CIB).

All Lego blocks were refactored to comply with the CIB restrictions. As soon as all class names were made unique, most rules came to use only a single class query and worked way faster.

<div class="b-head-logo">
    <span class="b-head-logo__name">
        Web Service Name Here
    </span>
</div>

Establishing Naming Conventions

After making several attempts to modify naming conventions, we agreed on principles that haven’t changed since.

In file names, the dot separator was replaced by a double underscore (__):

  • Before: b-block.elem.css
  • After: b-block__elem.css

Thus, file names were made consistent with CSS selectors.

Block elements were allowed to have their own modifiers, too. So, .b-block__elem_theme_green was similar to .b-block_theme_green.

Modifiers were changed to be a key-value pair:

  • Before: .b-menu__item_current
  • After: .b-menu__item_state_current

This change turned out to be useful for working with modifiers from JavaScript.

Going Open-Source (2010)

In 2010, we published some code on our GitHub account6 to continue growing as an open-source project.

Creating The BEM-BL Library

Blocks from Lego are being gradually ported to bem-bl7, a library of blocks that we consider useful for any website, not just Yandex projects. As blocks are gradually open-sourced, we improve code and add features.

This is very much a work in progress, and we invite everybody to make pull requests.

We’ve also developed bem-tools8, a set of helper scripts and automation utilities that make working with BEM files easier. This is mostly done with Node.js, to keep barriers low for front-end people who are familiar with JavaScript and are willing to contribute.

Redefinition Levels in BEM

One size never fits all… but one BEM does! Because blocks and elements are represented in a file system as files and folders, and BEM’s file structure is unified and based mostly on semantic criteria, we can easily redefine a part of a BEM block and add functionality. Similar to the way we extend objects in JavaScript, BEM blocks can be extended using so-called “redefinition levels.”

A typical redefinition level might be defined like this:

  1. The public bem-bl library pulled from GitHub, extended by…
  2. An internal block library (such as Lego), extended by…
  3. A project-specific block library.

You’re free to add more levels. Perhaps you need some page-specific block improvements… Oh, you get the idea.

For example:

bem-bl/
    b-logo/
lego/
    b-logo/
auto/
    blocks/
        b-logo/

Using a custom file structure for a particular redefinition level is also possible. As long as you follow the BEM concept, all you need to do is configure our building tools according to your cool new structure. We won’t go into much detail here, but there is a configuration file for this:

.bem/
    level.js

You could specify different file-naming patterns, or even flatten your folder structure completely.

BEMHTML Templating Engine

We tried different templating solutions and ended up developing our own, called BEMHTML.

This templating engine:

  1. Operates based on core BEM principles (block, element, modifier);
  2. Supports redefinition levels;
  3. Precompiles templates into JavaScript code that runs either in a browser or on a server.

More details on BEMHTML are available here (although in Russian):

BEM: Try This At Home!

As you can see, BEM has a long history of trial and error. It took Yandex a while to figure out what was important and what was not.

The foundation of the BEM methodology is block, element, modifier. These entities are used consistently in all of our projects.

BEM as we know and use it today is not the final answer, nor a revelation, but rather something constantly being driven by practice and tested in real-life projects. You can follow this to the extent that you find useful.

BEM is quite flexible, because it is mostly a methodology. There is no such thing as a BEM API or a BEM SDK. While we encourage you to try the open-source tools we provide, which are indeed a BEM framework, you might find that BEM principles are good enough to be embedded in your products or technologies in a different way.

Let’s discuss an example briefly.

Multiple Blocks in a Single File

Let’s assume you’ve got a Web project and want to give BEM a try by using it here and there in your HTML and CSS. That’s great. That’s how we started using BEM, too!

Choose the approach that you find the easiest to understand and to maintain. For example, you could give your block elements simple (non-prefixed) classes and then use modifiers with a key-value pair:

.b-block
.b-block .elem
.b-block_size_l
.b-block .elem_size_l

You could go one step further and assign a specific class to all DOM nodes in your block that have semantic meaning (those “completely independent blocks” that we talked about above):

.b-block
.b-block__elem
.b-block_size_l
.b-block__elem_size_l

Find the CSS prefixes too long to type? Remove them!

.block
.block__elem
.block_size_l
.block__elem_size_l

This is a perfect opportunity to try out BEM concepts. And because we don’t have strict rules, you can’t really break anything as long as you adhere to the main principle of block, element, modifier.

Establish a single file for each technology you use, and put all block declarations together:

myfacebook/
    myfacebook.css
    myfacebook.js
    myfacebook.html

You’ll have to support all of your changes manually at this stage (without bem-tools), but this could shorten the learning curve as well!

Blocks in a Separate Folder

As your project grows, you’ll find it more convenient to keep each block in a separate file. Just create an extra folder and put all block declarations in there:

blocks/
    b-myblock.css
    b-myblock.js
    b-yourblock.css
    b-yourblock.js

At this point, you’ll need to build your JavaScript and CSS files to combine multiple block declarations into a single one (i.e. gather all individual block styles into the project’s CSS file). Try bem-tools to see if you find them useful!

Making Things Optional

Some blocks might have elements or modifiers that are used only on certain pages or in particular scenarios. You can load optional elements separately to keep the core file small and neat:

blocks/
    b-myblock/
        b-myblock_mod_val1.css
        b-myblock__opt-elem.css
        b-myblock__opt-elem_mod_val1.css
        b-myblock.css

Modifiers in Folders

For blocks with many modifiers, put the modifiers into separate folders:

blocks/
    b-myblock/
        _mod/
            b-myblock_mod_val1.css
            b-myblock__opt-elem.css
            b-myblock__opt-elem_mod_val1.css
        b-myblock.css

This will make the block’s root folder easier to maintain.

Optional Elements in Folders

Block elements may also be made optional and get put in separate folders. This is an advanced, although quite flexible, approach.

blocks/
    b-myblock/
        _mod/
            b-myblock_mod_val1.css
        __opt-elem/
            b-myblock__opt-elem.css
        b-myblock.css

This is how we write the bem-bl library and most of the Lego blocks these days.

A Folder for Everything!

You can have a separate folder for each element and each modifier, be it optional or not. This is very logical and clear, but you might find this consistent structure a bit more difficult to maintain:

blocks/
    b-myblock/
        _mod/
            b-myblock_mod_val1.css
        __elem/
         b-myblock__elem.css
        b-myblock.css

You’ll be able to understand a block structure just from its folder structure, without even reading a single line of code. This is an unprecedented level of transparency, although it comes at a cost.

We have not yet fully decided to switch to this approach in Lego, but this is the most likely option.

Summary

There is no such thing as “true BEM,” and we don’t try to create one. The implementation we offer is consistent and we like it a lot, but you can create your own and still call it BEM, as long as you stay true to the core principles.

BEM is a collection of ideas and methods, a methodology. Companies and teams can integrate it into their existing workflow gradually, finding out what works best for them.

Credits

This article is based on an introductory presentation given by Vitaly Harisov14, one of the creators of the BEM methodology, at a Yandex.Saturday event in Minsk, Belarus, in 2011.

(al)

Footnotes

  1. 1 http://www.smashingmagazine.com/2012/04/16/a-new-front-end-methodology-bem/
  2. 2 http://my.ya.ru/
  3. 3 https://github.com/veged/borschik
  4. 4 https://github.com/bem/bem-identity/blob/master/sign/_theme/sign_theme_stripe.png
  5. 5 http://clubs.ya.ru/bem/replies.xml?item_no=338
  6. 6 https://github.com/bem
  7. 7 https://github.com/bem/bem-bl
  8. 8 https://github.com/bem/bem-tools
  9. 9 http://clubs.ya.ru/bem/replies.xml?item_no=898
  10. 10 http://clubs.ya.ru/bem/replies.xml?item_no=899
  11. 11 http://clubs.ya.ru/bem/replies.xml?item_no=1153
  12. 12 http://clubs.ya.ru/bem/replies.xml?item_no=1172
  13. 13 http://clubs.ya.ru/bem/replies.xml?item_no=1391
  14. 14 https://twitter.com/harisov

↑ Back to topShare on Twitter

Maxim Shirshin is a professional front-end developer with 10+ years of experience with complex web-based services and products for desktop and mobile. For the last several years, Maxim has been working on various Yandex services such as Yandex home page and Yandex.Direct. He recently relocated to Berlin where he works as a front-end team lead at Deltamethod GmbH. Maxim remains a consultant with Yandex and keeps himself involved with the BEM community.

Advertising
  1. 1

    BEM is useless when we have less and scss.

    -3
    • 2

      Nope. As an example, Harry Roberts uses BEM naming (and a bit of its block independence) in his awesome Sass-based Inuit.css framework.

      Also, preprocessors give a nice chance to shoot in your leg by using parent reference and nest the element selectors and that’s far from perfect in means of maintainability and perfomance.

      Preprocessors are cool, BEM is cool, together they’re even more cooler.

      1
    • 4

      Vladimir Grinenko

      February 22, 2013 2:36 am

      Could you please explain how less and sass helps with modular JS, automatical project build, templating, production optimization and a lot of other things BEM provides? :)

      0
      • 5

        Mihail Yakimenko

        March 3, 2013 12:43 pm

        BEM has nothing to do with templating. Paradigm, but ochesh strong. But all the rest, and tools including no sense.

        0
        • 6

          Varvara Stepanova

          March 6, 2013 9:18 pm

          That’s a little bit wrong :-) BEM provides its own templating solution called BEMHTML.

          0
    • 7
    • 8

      Well, with LESS and Sass, it’s even easier to practice BEM.

      0
      • 9

        This is especially so in LESS, where the following is valid (it doesn’t work in SASS any longer):
        .block {
            &__element {
                &--modifier {
                }
            }
        }

        Which produces:

        .block {}
        .block__element {}
        .block__element--modifier {}

        Which you can see makes for some very DRY and terse BEM syntax in LESS :)

        0
        • 10

          Nick,

          Can you explain why BEM uses two underscores or two dashes? People with small fonts in their text-editor of choice?

          0
          • 11

            Excuse the delay in response.

            The reason is to communicate intent. A double underscore is for an element, and a double dash is a modifier. Following this convention means that i can tell, at a glance, the intent and purpose of a selector. I must admit it looks pretty ugly, but it works.

            You don’t have to use double dash and double underscore, any valid separator works as long as you’re consistent (you’re probably limited by what constitutes a valid selector mind you)

            0
  2. 12

    I read the entire article and found it very interesting but ultimately did not feel a connection. This all seems very antiquated and irrelevant at this point. Though I’m sure BEM has many pro’s, I don’t really understand the point of any of this. Perhaps I just don’t understand, I fancy myself still (and always) a student so anyone feel free to enlighten me.

    0
  3. 15

    Max, thanks for this article! it is awesome and a bit nostalgic :)

    Remember how it was! You’re back from vacation and guys invent new naming convention, back from dinner: new tools for build files or new folder structure offered. It was crazy, but result is very inspiring.

    0
  4. 16

    This is so insightful!!

    Thanks a lot Maksim for writing and sharing this. Supercool.

    0
  5. 17

    Awesome to see an in-depth article on BEM. I’ve adopted a mix between BEM and SMACSS recently and really enjoy it. It adds a lot of clarity to the CSS I’m crafting for myself and future developers who work on a project I’ve worked on. Based on my understanding of SMACSS, I use different names for blocks, elements, and modifiers. I prefer module, component, modifier, but the concept is still pretty much the same.

    I’ve set up repo on github to document common naming patterns I’ve seen for blocks, elements, and modifiers.

    The class naming convention of BEM is a little different, but once you understand it, and see it’s potential, it is not too hard to switch over to.

    One of the things I like the most about BEM though is how flexible it is. You can use it as you need it and adjust to your own workflow as long as you follow the core principles.

    0
  6. 19

    Thanks for the roundup. I still feel that BEM complicates the simple. Unless BEM is baked into the next popular framework it doesn’t seem primed for widespread adoption.

    0
    • 20

      Vladimir Grinenko

      February 22, 2013 2:43 am

      Well, it depends on what your trying to do with it. If you would like to create a simple page you don’t need BEM but you don’t need any other framework neither. But as soon as you face a rapid developing projects with common components shared among them you’ll learn that BEM is very helpful and time saving.

      0
    • 21

      Dylan, you are completely right! It is exactly the way you’ve described. It complicates the simple.
      Moreover, this approach makes complex solutions even more complicated.

      I know what I’m talking about, I’ve worked with this guys (proponents and evangelists of BEM) for a couple of years and never found a common ground with them.

      Each time one is shouting: “Wait! This creates redundant complexity”, each time the answer was one of the following:

      * We’ll write a script that will simplify workflow (usually they don’t)
      * You just know nothing about complex javascript projects where you can gain profit using BEM (an literally no project was complex enough).
      * It’s OK.

      I wish you guys good and admire your attempts, since being stubborn is something we should respect. In a special kind of way )))

      0
      • 22

        There are ideas (methodology) and tools (implementation). The article is about the history of ideas; the existing Yandex implementation is neither perfect nor the only one possible. The methodology, however, is consistently used on almost any Yandex project, for many years, to at least some extent (with many key projects currently using the full BEM tool chain including templating solutions and automation).

        Accusing ex-colleagues of being “stubborn in a special way” doesn’t sound that cool, either, provided the fact you hardly know some of them personally, and disagreement on technology wasn’t exactly the reason you had to leave Yandex.

        -1
    • 23

      BEM’s primal target are developers, not the beginners who just need jquery there and bootstrap there. It is already used as one of the bases for Inuit.css framework.

      And anyway, the “Popular frameworks” walk the same way, but a bit slower — there were a lot of issues in Twitter Bootstrap which lead to the use of bem-like naming (see modifiers everywhere, media object etc.)

      0
      • 24

        Roman, can you please tell us a bit about how BEM is used in current Yandex projects you are currently working on? )))

        0
        • 25

          We’re using its naming, the concept of independent blocks and the file structure (with minor simplifications). But, yeah, only for CSS (actually Stylus). For other techs there are other things and there are concepts in BEM that I miss when I get into our templates.

          Also, the BEM changed a lot till you worked with it and there are plans on the further simplification of those concepts. Don’t be that harsh :)

          0
          • 26

            Roman,

            Don’t let the trolls get you down :)

            BEM is an *idea* bigger than any of our single uses of it. A principal, or set of principals really. So in this sense it’s like you’re trying to defend healthy eating.

            If you eat McDonalds, it’s going to make you fat ;) If you code shoddy code, which was not designed according to well tested ideas and principals, it’s going to make your programs suck :P So enjoy being fat and having crappy web apps!

            As for me, I am Fat, but I am also working out and getting healthier every day, and finally I’m eating right too… I am a crappy programmer, but I am also learning the principals to do it the right way. We don’t have to stay crappy, we all can improve.

            Thank you for developing BEM,
            -Jeremy

            1
          • 27

            You are not using it, Roman, since naming is NOT something you can name as a notable and self-consistent part of framework. I have nothing agains any consistent class naming.

            I’m not harsh, I’m sober :)

            0
  7. 28

    Sorry I think this is soo overly complicated.

    Just use OOCSS and make sure you JS and Server Side Components use the same naming convention. Much simpler and achieves exactly the same the results.

    Combine OOCSS with SASS or LESS and you are laughing.

    -1
  8. 29

    I totally agree with Dylan, untill it is not a part of popular framework.. it will remain inside a box and people will not adopt it that easily.

    btw, thanks for sharing the knowledge.

    0
    • 30

      Lokesh & Dylan,

      BEM should remain to be framework agnostic. It’s not about frameworks doing all of the work for you. BEM is about principaled-based programing techniques: such as TDD.

      Also, you should note that according to this article: http://en.wikipedia.org/wiki/Countries_by_area Russia is still the largest country in the world by landmass. Yandex is the largest web development for that entire region of the world, including other countries which still highly depend on the language, economic, cultural influence and power of Russia. According to my understanding BEM is one of the central cogs in the mechanism that is Yandex. They used BEM to create a framework that they themselves use, and maintain. Similar to http://twitter.github.com/bootstrap/

      So, I think it’s safe to say that BEM has already attached it’s self to a very prominent framework which is being used in the world today.

      It is interesting to note that even though BEM has been in use for a long time – it is only NOW that it has decided to show up in the rest of the world of developers. I think Meta Refresh was officially the second international presentation of BEM. I am currently learning BEM, and the moment I have a foothold strong enough to being speaking about it I will be evangelizing BEM here in America. As we can get more people involved with translating the documents I feel that BEM will continue to grow in strength and influence. BEM inherently solves problems that none of us have even spent time thinking about and I think it is worth our time and effort to understand what these great developers at Yandex have built.

      Respectively yours,
      -Jeremy

      0
      • 31

        Yelena Jetpyspayeva

        February 24, 2013 11:14 pm

        Dear Jeremy,

        so encouraging comment you wrote, thank you so much!! My team is so impressed now and have even more efforts and power to go ahead!

        Sincerely,

        Yelena

        0
        • 32

          Yelena,

          My pleasure :)

          I would like to add however that what should be even more encouraging than my efforts here (to help explain BEM to others as I see it) should be the fact that I’ve been working hours every day (since I finished my last project) reading as much material I can about BEM in Russian and English – trying to wrap my brain around how to give BEM a better voice in English. If you’re following the bem projects on github you’ll be seeing my updates. If you check out my github account (uberbuilder) you’ll see daily updates to bem documents as I get them ready for pull requests.

          My strategy is slow, and painful, re-wording as I work through the documents for the first time. I have to resist the urge to read ahead and finish the tutorials fast, which inhibits my wide-understanding of BEM, but keeps my view of the document fresh so that I can make sure we word it correctly for first-time readers. Maybe this is dumb, but it’s how I’ve been doing it.

          A good side-effect is that I now know more technical Russian than ever :) (also painful… I’ve been tempted to write my own google chrome translator so I can get translations by right clicking on words… if anyone knows about one please tweet it to me @jeremyi)

          Keep up the good work Яндекс:BEM people :)

          Cheers,
          -Jeremy

          0
  9. 33

    Personally I find some VERY interesting aspects to how BEM works, and how it solves a lot of communication problems. I’m not sure at first glance one would find it naturally appealing. Think about TDD for example. At first glance you think “Geez, TDD is going to double my development time”. As I hear it from the experts (I actually have some long-time personal friends who develop TDD frameworks) when one comes to be fluent with TDD it will end up doubling the amount of time you spend actually writing code. That’s right, you heard me – 200% your normal time coding. As it turns out we spend, as developers without TDD, 20% of our time coding and 80% of our time looking for where to code, or trying to figure out how to code. Once one learns TDD principles and becomes well adapt at applying them one will spend about 40% of his time writing code but it cots that 80% of time looking for problems/bugs in half, or more. That is a HUGE return on investment. And the bonus here is that now you have code that can pass a quality guarantee test! Which means you can deliver code that you can guarantee the quality of – 100%. But, if you just stop at “When someone programs applying TDD principals they spend two times more time coding and end up writing MORE code to do the SAME work because you have to write all those tests” then you miss out on the magic, you left five minutes before the miracle.

    BEM has that same kind of feeling to me.

    On top of that, it packages blocks together. Portable blocks of web goodness. That sounds good to me.

    I just got done watching the BEM presentation at MetaRefresh – I stayed up until the middle of the night to watch it (I’m near Philly, the Meta Refresh conference is in Bangalor). I’m not sure yet, I’m going to spend time researching this tomorrow… but I’m hoping that BEM can help me in generating live style guides for blocks of sites that I can use to deliver a living style guide to my clients and other developers on a project. If BEM can not only give me other benefits, but help me solve this problem too – then I think it’s a HUGE win.

    Plus, I think BEM is probably framework ready. Even the little research I’ve done on this BEM stuff I’ve seen that it has a standardized input for getting information INTO the block and it pumps out working well coded web stuffs. (Which include optimized code/css/html from a block programmed using TDD principals)

    This has develop-to-deployment goodness written all over it.

    I’m going to read this article tomorrow in depth, but I hope it will give me some information about how much BEM has helped the development process at Yandex.

    Also, another thing to note. I spent years in Ukraine – so I can just read the documentation (I’m “fluent” in Russian). I think the issue here is not that we need to make BEM plugin to frameworks or the other way around. I think we just need to understand what BEM is in the first place. Furthermore – I believe that a HUGE reason BEM was even created was to solve a language barrier issue with the fact that the vast majority of quality web development material is written in English. If we all spoke one language in this world I’m not sure things like BEM would exist. I think BEM helps solve an internationalization issue of cross-culture programming. After I learn how to use BEM I’m going to make it a point to translate as much documentation as I can so that we can get this thing out. I think this is possibly one of the next biggest innovations in web development.

    Good on you Yandex!

    Go BEM!

    -Jeremy

    0
    • 34

      Thank you Jeremy for the inspirational comment!

      In fact, you seem to understand the main idea of the article: BEM is the way to architecture web services, not a framework. Yes, there is a BEM framework developed at Yandex, which we try to promote, support and improve; a lot of examples we demonstrate are tied up to that particular implementation, for obvious reasons. However, as you embrace the key principles, it’s up to you to choose a particular tool chain, or help improve ours.

      We are looking forward to your contributions, they’ll be appreciated!

      0
      • 35

        Maxim,

        Perhaps this is a mistake of mine, but I find that I feel like Yandex is the “Russian version” of our “American” Yahoo (which could be argued does not belong to the American culture, but really the English speaking one). I’m sure you’ve heard of Douglas Crockford – In my mind he has a bunch of connections to Yahoo – but again, this might just show how little I actually know about Mr. Crockford. It seams like any time I get into a deep enough conversation with somebody about Javascript this guy’s name pops up. If you have not had a chance to watch this video, I think you should: http://www.yuiblog.com/blog/2010/02/03/video-crockonjs-1/

        In there Crockford talks about how new programming innovations happen once only every 20 years. Because, at the root of it, developers and programmers are lazy. We don’t want to change what we already know to work. Even if it’s for our own good. As individuals do have a great opportunity to change for the better, we seam to be stuck as a whole industry because of our human nature to seemingly fight change. Programmers are stubborn bunch, full of damning pride. We’re very young as an industry really. Compare how web-designers and programmers and developers go to school and learn how to design to how architects or lawyers or fine-artists learn. Many of us pick this other route because, well… we’re lazy. That is changing though.

        When one becomes an adult they find that in life, only those people who are truly highly effective live their life based on a set of principals. The same is true in programming. The truly successful and effective programmers use a set of principals to guide their work. I still need to spend time looking at BEM before I can say this is true about BEM being perfectly founded on true core principals which lead to successful projects. So, I’ll reserve my opinion until then. However – everything I have currently seen about BEM says that it is all about that.

        One of the thing that Crockford talks about in that video is how many really GOOD ideas never get accepted into the programming world. I hope that BEM really is in practice what I feel like it is after watching the Meta Refresh conference (over a horrible connection at 2:00am in the morning) and reading parts of your documentation. There are some idea’s in the back of my head that i’ve only spoken to few people about that I believe BEM solves right off the bat – maybe without even trying to. This idea I have is something I’ve been thinking about for about 10 years now. This is something we desperately need in our development patterns today, and I believe the market is finally ready for it. Once I complete some research on BEM I will return and write about it – a lot.

        Until then, keep your head up high and keep developing BEM. We need you!

        Don’t let the MILLIONS of doubters get you down when they don’t even know what they are missing out on.

        As for everyone else, unless you’ve made a company better than Yandex – maybe there is something we have to learn from these folks yeah?

        And to Douglas Crockford, if you ever read this – I dare you to comment about BEM!

        Cheers to the BEM team,
        -Jeremy

        1
    • 36
  10. 37

    What folder would I put classes that extend an abstraction in, and what should I prefix them with. For example, i have b-nav and am extending it with a class called “tabs.” Would “tabs” be prefixed the same way (“b-tabs”) and would it also be placed in the blocks folder? I’m not sure if you know what I mean, but here:

    [ul class="b-nav b-tab b-tab--bottom"]

    [/ul]

    (Sorry for using [ and ] for greater than and less than signs. It wouldn’t take)

    I would like to keep abstractions separate from the blocks for organization. How should I go about doing this.

    0
  11. 38

    honestly,

    only knowing about BEM through this article I can only say it seems outdated and hasn’t been adopted in popular frameworks like bootstrap.

    Sure bootstrap is not perfect but bagging it is a silly decision. BEM does not have the community that bootstrap does, so bootstraps flaws are more likely to be fixed up quicker than BEM. I’d like to see an example of a ‘complex’ project as separating each block element into a separate file seems like complete overkill in the provided examples. The common LESS workflows achieve the same thing much more efficiently.

    I’m in a research phase of developing a front-end tech framework and workflow methodology for a fairly large product I will not be adopting BEM.

    0
    • 39

      Most companies are stealing the naming convention and ditching the idea of a JavaScript library to read xml data. To me, reading xml to build out the site is completely useless. I would take a look at inuit.css which is a lot better than bootstrap in my opinion. Keep in mind there are some “design” problems — but not as bad as bootstrap. You will have to write skins for their abstraction objects.

      0
  12. 40

    If you are looking for a web design framework that uses BEM take a look at Adobe’s Topcoat – http://topcoat.io/

    I am doing more work on mobile web/apps so performance is key – if BEM helps then I will try it out. The folder schema does look quite extensive but if you take a look at component.io and the new PhoneGap plugin folders this seems to be a trend to help keep more things organised. Good? Bad? A bit of both but if it is automated then it won’t worry me much.

    Component.io quick start – http://vimeo.com/48054442
    PhoneGap 3.0.0 plugins – https://github.com/phonegap/phonegap-2-style-3/tree/master/plugins

    1

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top