/Cell

Style BEM DOM elements using Sass

Primary LanguageSCSSMIT LicenseMIT

GitHub license Build Status npm version npm downloads

Style BEM DOM elements using Sass [View SassDocs]

CodeSandbox Demos
Standard Demo JavaScript Demo React Demo Basic Sass-Only Demo

Overview

Cell is used for styling DOM elements that follow the Cell naming convention (which is almost identical to BEM).

Learn how to integrate with React components

Example

Given the following markup for an accordion with an active panel component:

View CodeSandbox Demo

Unlike traditional BEM, you do not need separate classes for modifiers

<div class="accordion">
  <div class="accordion__panel">
    <div class="accordion__title">foo</div>
    <div class="accordion__content">bar</div>
  </div>
  <div class="accordion__panel--active">
    <div class="accordion_title">fizz</div>
    <div class="accordion_content">buzz</div>
  </div>
</div>

This can be styled with Cell like so:

@include module('accordion') {
  @include component('panel') {
    ...

    @include is('active') {
      @include component('content') {
        display: block;
      }
    }
  }

  @include component('title') {
    ...
  }

  @include component('content') {
    ...
    display: none;
  }
}

Using context()

The above examples use the traditional cascading paradigm to apply styles under certain conditions. You can see that to show the content component above, the display property is applied in a cascading fashion inside the panel component.

Cell allows you to go about this in a dfferent way, allowing you to keep all styles pertaining to a single component in one place, thanks to the context() mixin, as seen in this example (this will produce identical CSS to the previous example):

@include module('accordion') {
  @include component('panel') {
    ...
  }

  @include component('title') {
    ...
  }

  @include component('content') {
    ...
    display: none;

    @include context(($this, 'panel'), 'active') {
      display: block;
    }
  }
}

Learn more about Cell Atoms

Continuing from the previous example, the display Atom can instead be used to handle the display property:

@include module('accordion') {
  @include component('panel') {
    ...
  }

  @include component('title') {
    ...
  }

  @include component('content') {
    ...
    @include display((($this, 'panel'), 'active'), block, none);
  }
}

Cell can interpret and parse CQ by passing a CQ compatible Sass map as the second parameter to the module() mixin, allowing the accordion example to be re-written as:

@include module('accordion', (
  'panel': (
    ...
  ),

  'title': (
    ...
  ),

  'content': (
    ...
    'display': none,

    'panel-is-active': (
      'display': block
    )
  )
));

Learn more about Cell and Cell Query (CQ)

Installation & Setup

npm install --save @onenexus/cell
// this path will vary depending on where the library is being imported
@import '../../node_modules/@onenexus/cell/dist/cell';

If you are using Node Sass, you can import the library anywhere using:

@import '~@onenexus/cell/dist/cell';

See the JavaScript Configuration page for instructions on how to use JavaScript/JSON configuration

Using with JavaScript

Cell can be used with JavaScript for things like theming and module configuration.

View CodeSandbox Demo

Using React?

Example

modules/
|--myModule/
|  |--config.js
|  |--styles.scss
themes/
|--myTheme.js
app.scss
themes/myTheme.js
export default {
  colors: {
    primary: '#00d4ff',
    secondary: '#58ed02'
  },
  breakpoints: {
    small: '720px',
    large: '1400px'
  }
}
modules/myModule/config.js
export default (theme) => ({
  name: 'myModule',
  background: theme.colors.primary,
  gutter: '1em'
});
modules/myModule/styles.scss
@import 'config.js';

@include module {
  display: block;
  margin-top: this('gutter');

  @media (min-width: theme('breakpoints', 'small')) {
    display: inline-block;
  }
}
app.scss
@import '~@onenexus/cell/dist/cell';
@import 'themes/myTheme.js';
@import 'modules/myModule/styles';
CSS Output
.myModule, [class*="myModule--"] {
  background: #00d4ff;
  display: block;
  margin-top: 1em;
}

@media (min-width: 720px) {
  .myModule, [class*="myModule--"] {
    display: inline-block;
  }
}

Note that the background property is output to CSS despite not being hard-coded inside styles.scss - this is because configuration properties that correspond to CSS properties can be automatically parsed as CSS - read the Cell Query page to learn more

Read the JavaScript Configuration page for setup instructions and more information

Using with React

Using Cell with React can be as simple as configuring your Webpack to use Sass-Loader. See how the below React accordion component can be styled by importing its corresponding Cell module (styles.scss):

View CodeSandbox Demo

modules/Accordion/index.js
import React, { useState } from 'react';
import './styles.scss';

const Accordion = ({ panels, ...props }) => {
  const [activeIndex, toggle] = useState(0);

  return (
    <div className='accordion' { ...props }>
      {panels.map(({ heading, content }, index) => (
        <div className={`accordion__panel${index === activeIndex ? '--active':''}`}>
          <div className='accordion__title' onClick={() => toggle(index)}>
            {title}
          </div>

          <div className='accordion__content'>
            {content}
          </div>
        </div>
      ))}
    </div>
  );
}

export default Accordion;

Using with Lucid (React Library)

Lucid is a React library for working with the Cell/BEM naming convention. If using Lucid, the above React component could be rewritten as:

import React, { useState } from 'react';
import { Module, Component } from '@onenexus/lucid';
import './styles';

const Accordion = ({ panels, ...props }) => {
  const [activeIndex, toggle] = useState(0);

  return (
    <Module name='accordion' { ...props }>
      {panels.map(({ heading, content }, index) => (
        <Component name='panel' active={index === activeIndex}>
          <Component name='heading' onClick={() => toggle(index)}>
            {heading}
          </Component>

          <Component name='content'>
            {content}
          </Component>
        </Component>
      ))}
    </Module>
  );
}

export default Accordion;

This solution offers all the practical benefits of scoped styling (thanks to the underlying Cell/BEM naming convention) without any of the uglyness that BEM usually brings, and without any of the overhead that CSS-in-JS techniques (and actual scoping) bring, keeping everything clean and tidy.

Useful Wiki Pages

Mixins

Cell comes with the following mixins to help create and structure your modules in the most efficient way possible:

Utility Functions

BEM Inspired Motivation

The initial motiviation behind creating Cell is twofold:

  • Address the uglyness of BEM
  • Address the practical implementation of BEM using Sass

BEM solves very real problems like no other solution due to its inherent nature, however it is often considered quite ugly; the __ and -- thrown into your HTML along with repeated keywords when using modifiers (block__component block__component--modifier-1 block__component--modifier-2) make the HTML extremely jarring to look at. Cell solves these issues by abstracting the logic into mixins and making use of CSS's wildcard attribute selector.

Since the initial conception, Cell has evolved to become a fully-fledged framework for writing scalable and maintainable CSS.