/bem

✨ Magically generates class names for React components

Primary LanguageTypeScriptMIT LicenseMIT

Build Status Coverage Status Language grade: JavaScript Package dependencies npm bundle size Maintenance npm version

BEM

css modules + bem + reat = love

Magically generates class names for React component.

Installation

npm install @textkernel/bem --save

or

yarn add @textkernel/bem

Usage

  1. 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: '_',
});
  1. Import bem into a React component, create block and elem 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;
  1. 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;
    }
}

Examples of outcome

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.

No props:

<Button />
 ↓ ↓ ↓
<button class="Button">
    <span class="Button__label" />
</button>

Prop active is set:

<Button active={true} />

    ↓ ↓ ↓

<button class="Button Button--active">
    <span class="Button__label Button__label--active" />
</button>

Prop active and type are set:

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>

Prop active equals false

No classnames will be produced if boolean property has false value.

<Button active={false} />

    ↓ ↓ ↓

<button class="Button">
    <span class="Button__label" />
</button>

Clicked state

<Button /> <!-- this.setState({ clicked: true }) -->

    ↓ ↓ ↓

<button class="Button Button--clicked">
    <span class="Button__label Button__label--clicked" />
</button>