use-bem
v2.0.0
Published
Framework-agnostic BEM class name generation, with an optional React hook.
Maintainers
Readme
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-bemReact 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 insteadDisabled keys in a boolean record ({ foo: false }) aren't validated, since they never reach the output.
API
createBem
createBem(block: string, config?: BemConfig): BemFunctionCreates 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.
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.
BemConfig
interface BemConfig {
elementSeparator?: string; // default: '__'
modifierSeparator?: string; // default: '--'
}createBemFactory
createBemFactory(config?: BemConfig): (block: string) => BemFunctionReturns a createBem variant with the given separators baked in.
useBem (from use-bem/react)
useBem(block: string, config?: BemConfig): BemFunctionMemoized 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
