Skip to content

djurnamn/use-bem

Repository files navigation

use-bem

Framework-agnostic BEM class name generation, with an optional React hook.

The core is one small pure function with no dependencies. You give it a block and it builds the block__element--modifier strings for you, so you're not hand-writing the same class names or wiring up conditional logic for every modifier. It runs the same in React, Vue, Svelte, or plain JavaScript, and the output is identical everywhere.

Installation

npm install use-bem
# or
pnpm add use-bem
# or
yarn add use-bem

React is an optional peer dependency. It's only needed if you import use-bem/react.

Core usage

import { createBem } from 'use-bem';

const bem = createBem('badge');

bem();                                  // -> "badge"
bem('label');                           // -> "badge__label"
bem('label', 'large');                  // -> "badge__label badge__label--large"
bem('label', ['large', 'primary']);     // -> "badge__label badge__label--large badge__label--primary"
bem(undefined, { solid: true, soft: false }); // -> "badge badge--solid"

Modifier output always includes the base class, so the result drops straight into a class attribute as-is.

Framework examples

React

import { useBem } from 'use-bem/react';

const Badge = ({ solid }: { solid?: boolean }) => {
  const bem = useBem('badge');

  return (
    <span className={bem(undefined, { solid })}>
      <span className={bem('label')}>New</span>
    </span>
  );
};

useBem is createBem wrapped in useMemo, so the generated function is stable across re-renders. It calls useMemo unconditionally and is SSR-safe (Next.js, Remix, and so on).

Vue

<script setup lang="ts">
import { createBem } from 'use-bem';

defineProps<{ solid?: boolean }>();

const bem = createBem('badge');
</script>

<template>
  <span :class="bem(undefined, { solid })">
    <span :class="bem('label')">New</span>
  </span>
</template>

Svelte

<script lang="ts">
  import { createBem } from 'use-bem';

  export let solid = false;

  const bem = createBem('badge');
</script>

<span class={bem(undefined, { solid })}>
  <span class={bem('label')}>New</span>
</span>

Outside React there's no wrapper to reach for. createBem runs once in component setup and the returned function is just string concatenation.

Custom separators

Pass a config object as the second argument:

const bem = createBem('block', {
  elementSeparator: '-',  // default: '__'
  modifierSeparator: '_', // default: '--'
});

bem('element', 'modifier'); // -> "block-element block-element_modifier"

For app-wide separators, create a preconfigured factory once and import it everywhere:

// utilities/bem.ts
import { createBemFactory } from 'use-bem';

export const createBem = createBemFactory({
  elementSeparator: '-',
  modifierSeparator: '_',
});

// Component file
import { createBem } from './utilities/bem';

const bem = createBem('block');

The React hook takes the same config: useBem('block', { elementSeparator: '-' }).

Validation

Block, element, and modifier names may only contain letters, digits, hyphens, and underscores. Invalid names throw:

createBem('my block');         // Error: should not contain spaces
bem('label', 'large primary'); // Error: pass an array of strings instead

Disabled keys in a boolean record ({ foo: false }) aren't validated, since they never reach the output.

API

createBem

createBem(block: string, config?: BemConfig): BemFunction

Creates a class name generator for a BEM block. Validates the block name immediately.

BemFunction

type BemFunction = (
  element?: string,
  modifier?: string | string[] | Record<string, unknown>
) => string;
  • Call with no arguments for the block class.
  • element is the BEM element name; undefined targets the block itself.
  • modifier is a single name, an array of names, or a record whose keys are applied when their value is truthy. The record value type is unknown because only truthiness is read, so an optional boolean (boolean | undefined) or any condition expression works directly, with no Boolean(...) wrapping.

BemConfig

interface BemConfig {
  elementSeparator?: string;  // default: '__'
  modifierSeparator?: string; // default: '--'
}

createBemFactory

createBemFactory(config?: BemConfig): (block: string) => BemFunction

Returns a createBem variant with the given separators baked in.

useBem (from use-bem/react)

useBem(block: string, config?: BemConfig): BemFunction

Memoized React hook over createBem. Requires React 16.8 or newer.

Migrating from 1.x to 2.0

2.0 makes the core framework-agnostic. The pure createBem function is now the main entry, and the React hook moved to a subpath.

1.x 2.0
import useBem from 'use-bem' import { useBem } from 'use-bem/react'
import { createBemHook } from 'use-bem'; createBemHook(config) returns a hook import { createBemFactory } from 'use-bem'; returns a createBem variant, or pass config directly: createBem(block, config) / useBem(block, config)
useBem(block) unchanged (after updating the import)

Generated class names are identical between 1.x and 2.0, so no CSS changes are needed.

- import useBem from 'use-bem';
+ import { useBem } from 'use-bem/react';

If you used createBemHook for custom separators:

- import { createBemHook } from 'use-bem';
- const useBem = createBemHook({ elementSeparator: '-' });
  // in the component:
- const bem = useBem('block');
+ import { useBem } from 'use-bem/react';
  // in the component:
+ const bem = useBem('block', { elementSeparator: '-' });

License

MIT © Björn Djurnamn

About

A React hook for generating BEM class names.

Resources

License

Stars

0 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors