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.
npm install use-bem
# or
pnpm add use-bem
# or
yarn add use-bemReact is an optional peer dependency. It's only needed if you import use-bem/react.
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.
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).
<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><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.
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: '-' }).
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 insteadDisabled keys in a boolean record ({ foo: false }) aren't validated, since they never reach the output.
createBem(block: string, config?: BemConfig): BemFunctionCreates a class name generator for a BEM block. Validates the block name immediately.
type BemFunction = (
element?: string,
modifier?: string | string[] | Record<string, unknown>
) => string;- Call with no arguments for the block class.
elementis the BEM element name;undefinedtargets the block itself.modifieris a single name, an array of names, or a record whose keys are applied when their value is truthy. The record value type isunknownbecause only truthiness is read, so an optional boolean (boolean | undefined) or any condition expression works directly, with noBoolean(...)wrapping.
interface BemConfig {
elementSeparator?: string; // default: '__'
modifierSeparator?: string; // default: '--'
}createBemFactory(config?: BemConfig): (block: string) => BemFunctionReturns a createBem variant with the given separators baked in.
useBem(block: string, config?: BemConfig): BemFunctionMemoized React hook over createBem. Requires React 16.8 or newer.
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: '-' });MIT © Björn Djurnamn