Skip to content

Instantly share code, notes, and snippets.

@chriskrycho
Last active December 9, 2017 15:58
Show Gist options
  • Save chriskrycho/e3eec1761accac263f0da3e3a5b230a2 to your computer and use it in GitHub Desktop.
Save chriskrycho/e3eec1761accac263f0da3e3a5b230a2 to your computer and use it in GitHub Desktop.
BEM in Ember

We just wrote a very simple library (which I hope to open-source by the end of the year) and we write our components like this:

import bem from 'our-app/lib/bem';

// so we can easily grab it in tests
export default block = 'some-component';

export default Component.extend({
  classNameBindings: ['tagClass'],
  tagClass: bem({ block }),
  aSubElementClass: bem({ block, element: 'look-an-el' }),
  withModifiers: bem({ block, modifiers: ['neat', 'weird'] }),
  withElAndModifiers: bem({ block, element: 'child-thing', modifiers: ['cool'] }),

  somethingToWatch: true,
  computingModifiers: computed('somethingToWatch', function() {
    const modifiers = this.get('somethingToWatch') ? ['turn-it-on'], : [];
    return bem({ block, element: 'whoa', modifiers });
  }),
});

Then we just use those classes on the component. (The component element itself gets the tagClass via the classNameBindings attribute.)

<div class={{aSubElementClass}}>yay</div>
<div class={{withModifiers}}>yay</div>
<div class={{withElAndModifiers}}>yay</div>
<div class={{computingModifiers}}></div>

So at render, that ends up looking like this if somethingToWatch is false:

<div id="ember123" class="some-component">
  <div class="some-component__look-an-el">yay</div>
  <div class="some-component some-component--neat some-component--weird">yay</div>
  <div class="some-component__child-thing some-component__child-thing--cool">yay</div>
  <div class="some-component__whoa"></div>
</div>

And then if somethingToWatch is true, it looks like this:

<div id="ember123" class="some-component">
  <div class="some-component__look-an-el">yay</div>
  <div class="some-component some-component--neat some-component--weird">yay</div>
  <div class="some-component__child-thing some-component__child-thing--cool">yay</div>
  <div class="some-component__whoa some-component__whoa--turn-it-on"></div>
</div>

We played for a while with having a BemComponent we extended from, so that the classNameBindings stuff got set up automatically, but ended up just reverting to doing it explicitly. We did write a blueprint that generates much of it automatically for us though, so if we just do ember generate bem-component example-component we get this - the attributeBindings are there for use with ember-test-selectors

import Component from '@ember/component';

import { bem } from 'mobile-web/lib/utilities/bem';

export const block = 'example-component';

export default Component.extend({
  // `attributeBindings` and `classNameBindings` must be here until we have TS
  // support for the new decorator spec, as they need to be merged with other
  // items of the same name in the prototype chain.
  attributeBindings: [`elementId:data-test-${block}`],

  // You should remove `classNameBindings` and `tagClass` if you're going to
  // apply the BEM root to an element in the template explicitly.
  classNameBindings: ['tagClass'],

  tagClass: bem({ block }),
})

In practice, that's worked extremely well for us. As you can see from the invocation here, the bem function we use just expects an options argument with at least the block - it'll error otherwise - and either or both of the elements and modifiers options.

Fairly soon, I'll also be adding SCSS template to the blueprint – we're in the midst of some changes around how we build our CSS, after which I'll automatically generate an _example-component.scss with the root class automatically generated and add it to our root styles.scss as well.

(A couple qualifications on this: we actually are generating it slightly differently, as an ES6 class, and we're doing it in TypeScript, but this is the gist.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment