A simple JavaScript utility for generating classnames following the BEM principles.
Usage:
import bem from "bero";
bem("button", "label", ["visible", "active"]);
// => 'button__label button__label--visible button__label--active'
The library can also be used directly on the page just including the index.js
in a standalone <script>
tag; or by RequireJS.
I found repeating myself a lot when I was working on a React project following the BEM structure and naming convention; even using library such classnames
.
I wanted a similar non-invasive approach as classnames
lib, but following the BEM naming convention, and that's the reason behind the creation of bero
.
It's a reference to Yōkai Ningen Bem (妖怪人間ベム Yōkai Ningen Bemu, translated officially as Humanoid Monster Bem), a 1968 Japanise anime that I used to watch when I was a kid. The main characters were Bem, Bera and Bero.
The bem
function is a curried function, takes up to three arguments.
The simplest usage is the basic signature with two arguments, identifier
and modifiers
:
bem(identifier: String, modifiers: Array|Object) : String
The identifier
can be either a block
, or an element
.
If an element
is specified, the full identifier has to be written, in the form of block__elem
. For example:
bem("button__label", ["visible", "active"]);
// => 'button__label button__label--visible button__label--active'
Since bem
is a curried function, it also possible write the code above as:
const label = bem("button__label");
label(["visible", "active"]);
// => 'button__label button__label--visible button__label--active'
This form would be rarely used for elements; it's more common having block functions, when the signature with three arguments is used:
bem(block: String, elem: String, modifiers: Array|Object) : String
In this form, the equivalent of the code above would be:
bem("button", "label", ["visible", "active"]);
// => 'button__label button__label--visible button__label--active'
But it would be more common used as curried function for block functions:
const button = bem("button");
button("label", ["visible", "active"]);
// => 'button__label button__label--visible button__label--active'
This form is useful especially in components, where there is likely only one block per component, but multiple elements as children of that block.
The modifiers
arguments can be either an Array
or an Object
.
The logic is the same of @JedWatson's classnames module.
If it's an Array
, every element that is considered truthy, would be
added as modifier in the resulting classname:
bem("button__label", [false, "visible", 0, , "", undefined, "active"]);
// => button__label button__label--visible button__label--active'
However, modifiers
really shines when an Object
is given:
bem("button__label", {
visible: isVisible,
active: isActive
});
// with `isVisible`: true, `isActive`: true
// => button__label button__label--visible button__label--active'
// with `isVisible`: true, `isActive`: false
// => button__label button__label--visible'
// with `isVisible`: false, `isActive`: true
// => button__label button__label--active'
// with `isVisible`: false, `isActive`: false
// => button__label
With computed property names you can also have modifiers as such:
bem("button__label", {
[`text-${color}`]: !!color
});
// with `color`: undefined:
// => button__label
// with `color`: "red"
// => button__label button__label--text-red
All modifiers are automatically converted from camel case to kebab case:
bem("button__label", {
hasFocus
});
// with `hasFocus`: true
// => button__label button__label--has-focus
bem("button__label", ["ColorRed"]);
// => button__label button__label--color-red
bem("button__label", ["DOMLoaded"]);
// => button__label button__label--dom-loaded
Any numbers of hyphen at the beginning of the string would be removed:
bem("button__label", ["-foo", "--bar", "---baz"]);
// => button__label button__label--foo button__label--bar button__label--baz
And any numbers of hyphen inside the string would be reduced to one:
bem("button__label", ["foo----bar"]);
// => button__label button__label--foo-bar
So that even in those edge cases the BEM naming convention is kept.
bero
comes with an utility function that helps to concatenate several truthy
values in one string. That's useful when the generated BEM classname needs to
be concatenate by external strings, such a className
passed by props in React. See below for a real-world example.
import bem, { join } from "bero";
const button = bem("button");
export default class Button {
// ...
render() {
const { pressed, hover } = this.state;
const { className, label, onClick } = this.props;
return (
<button
className={join(button({ pressed, hover }), className)}
onClick={onClick}
>
<label className={button("label", ["strong"])}>{label}</label>
</button>
);
}
}
MIT. Copyright (c) 2018 Matteo Ferretti