Magically generates class names for React component.
npm install @textkernel/bem --save
or
yarn add @textkernel/bem
- Create and export your own bem function using
make
.
// initBem.js
import make from 'bem';
// `make` allows you to customize bem prefixes
export default make({
elemPrefix: '__',
modPrefix: '--',
valuePrefix: '_',
});
- Import bem into a React component, create
block
andelem
functions and use them in render method
// Button.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import bem from './initBem';
import classnamesMap from './Button.scss';
const { block, elem } = bem(
'Button', // Block name
classnamesMap // Class names dict generated by CSS modules loader
);
const Button = (props) => (
<button
{/* If needed, `props` should be spreaded before `block` or `elem in order to avoid className overwrite. */}
{...props}
{/* Spread `block` to add class names to the top level node */}
{ ...block(props) }
>
<span
{/* Spread `elem` to add class names to an element. */}
{ ...elem('label', props) }
>
{props.children}
</span>
<span
{/* Custom modifiers combined with props. */}
{...elem('icon', {
...props,
almostRandomValue: 42
})}
>
{props.children}
</span>
</button>
);
Button.propTypes = {
active: PropTypes.bool,
};
Button.defaultProps = {
active: false,
};
export default Button;
- Write css respecting BEM methodology and it will be automatically picked up.
/* Button.scss */
/* Component's root node class name */
.Button {
display: inline-block;
/*
Block: "Button", modifier: "active" (based on props.active), value: true.
Is applied to the component's root node when props.active = true is set.
*/
&--active {
color: red;
}
/*
Block: "Button", modifier: "type" (based on props.type), any truthy value.
Is applied to the component's root node when `props.type = "normal"` is set.
*/
&--type {
border: 1px;
}
/*
Block: "Button", modifier: "type" (based on props.type), value: "normal".
Is applied to the component's root node when `props.type = "normal"` is set.
*/
&--type_normal {
background-color: grey;
}
/*
Block "Button", modifier "type" (based on props.type), value "extraordinary".
Is applied to the component's root node when `props.type = "extraordinary"` is set.
*/
&--type_extraordinary {
background-color: red;
}
/*
Block "Button", modifier "clicked" (based on state.clicked), value true.
Is applied to the component's root node when `state.clicked = true` is set.
*/
&--clicked {
border-style: dashed;
}
/*
Block "Button", element "label"
Is applied to the component's label node.
*/
&__label {
color: blue;
}
/*
Block "Button", element "label", modifier: "active" (based on props.active), value: true.
Is applied to the component's label node when props.active = true is set.
*/
&__label--active {
color: yellow;
}
/*
Block "Button", element "label", modifier "extraordinary" (based on props.type), value "extraordinary".
Is applied to the component's label node when `props.type = "extraordinary"` is set.
*/
&__label--type_extraordinary {
color: orange;
}
}
Having the example above we can get the following results.
bem
decorator adds only classnames that are declared in a stylesheet and
respectively exists in classnames map.
<Button />
↓ ↓ ↓
<button class="Button">
<span class="Button__label" />
</button>
<Button active={true} />
↓ ↓ ↓
<button class="Button Button--active">
<span class="Button__label Button__label--active" />
</button>
Note that property of a boolean type active={true}
produces Button__label--active
(without mod value), when property of a string type type='extraordinary'
gives us two classnameas: Button__label--type
(without mod value) and Button__label--type_extraordinary
(with mod value).
<Button active={true} type='extraordinary' />
↓ ↓ ↓
<button class="Button Button--active Button--type Button--type_extraordinary">
<span class="Button__label Button__label--active Button__label--type Button__label--type_extraordinary" />
</button>
No classnames will be produced if boolean property has false
value.
<Button active={false} />
↓ ↓ ↓
<button class="Button">
<span class="Button__label" />
</button>
<Button /> <!-- this.setState({ clicked: true }) -->
↓ ↓ ↓
<button class="Button Button--clicked">
<span class="Button__label Button__label--clicked" />
</button>