Style BEM DOM elements using Sass [View SassDocs]
CodeSandbox Demos | |||
---|---|---|---|
Standard Demo | JavaScript Demo | React Demo | Basic Sass-Only Demo |
Cell is used for styling DOM elements that follow the Cell naming convention (which is almost identical to BEM).
- Cell is used for creating modular, configurable and scalable Sass components
- Works with any Sass implementation (Ruby, Dart, Node...)
- Requires Sass 3.4+ (4.9+ if using Node-Sass)
- Import themes/configuration into your Sass/Cell components as JavaScript/JSON
- Built for the Synergy framework
- Compatible with Cell Query
Given the following markup for an accordion with an active panel component:
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;
}
}
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;
}
}
}
Using 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);
}
}
Using Cell Query (CQ)
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
)
)
));
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
Cell can be used with JavaScript for things like theming and module configuration.
modules/
|--myModule/
| |--config.js
| |--styles.scss
themes/
|--myTheme.js
app.scss
export default {
colors: {
primary: '#00d4ff',
secondary: '#58ed02'
},
breakpoints: {
small: '720px',
large: '1400px'
}
}
export default (theme) => ({
name: 'myModule',
background: theme.colors.primary,
gutter: '1em'
});
@import 'config.js';
@include module {
display: block;
margin-top: this('gutter');
@media (min-width: theme('breakpoints', 'small')) {
display: inline-block;
}
}
@import '~@onenexus/cell/dist/cell';
@import 'themes/myTheme.js';
@import 'modules/myModule/styles';
.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 insidestyles.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 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
):
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;
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.
Cell comes with the following mixins to help create and structure your modules in the most efficient way possible:
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.