The past week or so I've spent quite a lot of time putting together react-tabbordion component. It is my first contribution to npm and also the first time I've managed to put up WebPack and tests running on my own; this happened only now as we finally decided at my work to separate some concerns and go for independent React components. This allowed for me to work at home, which "surprisingly" increased my productivity. But enough rambling: we're now off to BEM.

What is wrong

Styling things can get complicated. One of the hardest things in CSS to deal with is specificity and the global nature, which has lead to solutions like BEM. Of course we're already talking about things like local CSS, but as I'm not there yet I wanted to see how far BEM can go with React.

One of the common issues is that you may have technically very or fully similar components and thus you instead want to have multiple styles for a single component. One of my earlier React components controlled style from container's class:

  .accordion > .panel {
    // one kind of style here
}

.tabs > .panel {
    // other kind of style here
}

One might think this makes things a bliss: you can change just one className and get a different appearance. However, on CSS side the rules can get very long, especially if BEM is being used...

  .tabs > .panel > .panel__content-container > .panel__content { ... }

The speficity has to be precise, because without using sibling selector styles would mix when there are components within other components. All the issues are because of abandoning a core mechanism of BEM and a style rule is controlling a block outside one. So the only way around is to go ahead and get that container component className to inherit to the child components.

Inheritance in React

Previously I wasn't aware of some of React's features. This was due to my disinterest to React addons, but things have since been moved to React's core and that is a good thing. The two things that are important to know are React.Children and React.cloneElement. The first one allows looping through child elements and the later one allows manipulation of the child elements by giving them new properties and/or replacing their children. This of course isn't real inheritance if we go for purism, but the practical effect is that we can let child elements follow a specific className.

  render: function() {
    var block = 'container'

    return <div className={block}>
        {React.Children.map(this.props.children, function(child, index) {
            if (!child) return child

            if (child.type === ChildComponent) {
                child = React.cloneElement(child, { block: block })
            }

            return child
        })}
    </div>
}

And now in child component's render we can use this new property:

  render: function() {
    var blockElement = this.props.block + '__child'

    // for sample this component automatically wraps all it's child elements to list elements
    return <ul className={blockElement}>
        {React.Children.map(this.props.children, function(child, index) {
            return <li className={blockElement + '-item'}>{child}</li>
        })}
    </ul>
}

And now we have two components with three classes:

  1. container
  2. container__child
  3. container__child-item

Ease of use

With Tabbordion I haven't implemented what I wrote above. Instead I wanted to give the component's user full customization ability without a need to continuously repeat the same stuff for each rendered Panel child component. Thus I implemented the following property into the parent component:

  childNames: {
    panel: 'panel',
    state: 'panel__state',
    title: 'panel__title',
    content: 'panel__content'
}

The above are the defaults, and they don't really have a container block. This is because by default Tabbordion doesn't have any className set. Maybe this will encourage users of the component to come up with their own names.

In practise this makes for quite clean syntax in my opinion:

  // note: state is omitted here because we don't need to style it -> it won't have a className
var classNames = {
    panel: 'container__panel',
    title: 'container__title',
    content: 'container__content'
}

return <Tabbordion className="container" classNames={classNames} ...>
    <Panel title={<span>I have no content</span>}" />
    <Panel title={<span>I have content</span>}">
        <p>Oh I'm content</p>
    </Panel>
</Tabbordion>

Modifiers

One of the annoying things with CSS selectors is that they account for all kinds of elements. For example, :last-child will select the last child element even if it has display: none;. That is quite annoying when using border-radius and you only want to round the last visible element's bottom or right side borders.

BEM has modifiers in the -- syntax, so one can do .panel--last to indicate that this is the last visible child, thus making a selector available in CSS. Having used modifiers for a while I noticed I don't really want to do something like this:

  var classNames = {
    panel: 'container__panel',
    title: 'container__title',
    content: 'container__content'
}

return <Tabbordion className="container" classNames={classNames} ...>
    <Panel className="container__panel--first" title={<span>I have no content</span>}" />
    <Panel className="container__panel--last" title={<span>I have content</span>}">
        <p>Oh I'm content</p>
    </Panel>
</Tabbordion>

This kind of thing should be automated! So I expanded the previous idea and ended up creating classModifiers:

  classModifiers = {
    checked: 'checked',
    unchecked: 'unchecked',
    disabled: 'disabled',
    enabled: 'enabled',
    visibleFirst: 'first',
    visibleBetween: 'between',
    visibleLast: 'last'
}

Then these modifiers are automatically applied in code logic inside the Panel child component to every class created. End result is like this:

  1. panel panel--checked panel--enabled panel--first
  2. panel panel--unchecked panel--enabled panel--between
  3. panel panel--unchecked panel--disabled panel--between
  4. panel panel--unchecked panel--enabled panel--last

The same replicates for panel__title and panel__content, which of course depend on what is given in classNames object.

Using CSS to target these is easy and convenient. And while on isomorphic React sites the HTML output becomes lengthy it will also compress very well when gzipped, because same things repeat near each other. However, if some feature isn't being used then removing it is quite easy:

  var classNames = {
    panel: 'container__panel',
    title: 'container__title',
    content: 'container__content'
}

var classModifiers = {
    checked: 'checked',
    disabled: 'disabled',
    visibleLast: 'last'
}

return <Tabbordion className="container" classNames={classNames} classModifiers={} ...>
    <Panel title={<span>I have no content</span>}" />
    <Panel title={<span>I have content</span>}">
        <p>Oh I'm content</p>
    </Panel>
</Tabbordion>

With the above the classNames generated would be like:

  1. container__panel container__panel--checked
  2. container__panel container__panel--last

And this isn't all. You can still use className as well to inject custom modifiers (or whatever classes)!

  return <Tabbordion className="container" classNames={classNames} classModifiers={} ...>
    <Panel className="panel__container--custom" title={<span>I have no content</span>}" />
    <Panel title={<span>I have content</span>}">
        <p>Oh I'm content</p>
    </Panel>
</Tabbordion>

And result would be like:

  1. container__panel container__panel--checked container__panel--custom
  2. container__panel container__panel--last

It is also possible to use modifiers and custom classes with classNames object:

  var classNames = {
    panel: 'container__panel container__panel--custom custom',
    title: 'container__title',
    content: 'container__content'
}

In which case all Tabbordion's container__panels would have .custom and .container__panel--custom.

I'm quite proud of implementing this system so while this kind of complexity won't happen with local CSS in future I still find this worthy enough implementation to write about (and maybe even promote).

Future ideas

I think this can still be improved on. One of the things that is annoying when writing BEM style classes is the length of them. While on CSS this issue will remain it is possible to let JavaScript do some work for us.

It would be nice to write just this:

  var classNames = {
    panel: 'container__panel --custom custom',
    title: 'container__title',
    content: 'container__content'
}

or this:

  return <Tabbordion className="container" classNames={classNames} classModifiers={} ...>
    <Panel className="--custom --another-custom" title={<span>I have no content</span>}" />
    <Panel title={<span>I have content</span>}">
        <p>Oh I'm content</p>
    </Panel>
</Tabbordion>

And have those -- modifiers automatically expand to their full glory. I might implement this soon to Tabbordion.

But it might also be an idea to export all this functionality out of Tabbordion for reuse. What do you say: do you want to use this kind of thing in your own components? Or is there already a similar or same thing done and I've simply been unaware of it's existence?