npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

typed-bem

v1.0.2

Published

A TypeScript library for generating BEM class names.

Readme

Typed BEM

npm license downloads issues pull requests size contributors

Overview

Typed BEM extends the battle-tested easy-bem utility with first-class TypeScript support. You describe the valid blocks, elements, and modifiers once, and Typed BEM turns that definition into:

  • type-safe class name factories,
  • helper utilities for composing class lists, and
  • an optional SCSS file generator that mirrors your schema.

By centralizing your BEM schema in TypeScript you remove guesswork, prevent typos, and keep styles synchronized across your project.

Reference Project

This library is used in the KoliBri project - an accessible web component library that provides a comprehensive set of UI components following accessibility best practices. KoliBri demonstrates real-world usage of Typed BEM for maintaining consistent and type-safe BEM class names across a large component library.

Key Features

  • Type-Safe API – Catch invalid blocks, elements, or modifiers during development instead of at runtime.
  • Set-Based Modifiers – Model allowed modifiers with Set<string> for fast lookups and precise literal unions.
  • SCSS Generator – Produce starter SCSS files that follow the same structure as your TypeScript schema.
  • Tree-Shakeable HelpersgenerateBemClassNames, uniqueClassNames, and generateBemScssFile are exported individually.
  • Lightweight Footprint – Built directly on top of easy-bem with no extra runtime dependencies.

Installation

Install the package with your preferred package manager:

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

Quick Start

1. Describe your schemas

Define a TypeScript type that captures the blocks, their elements, and the allowed modifiers. Use Set when modifiers are allowed and null when they are not. Best Practice: Define all your components in a central schema for better maintainability.

import { generateBemClassNames } from 'typed-bem';

// Central schemas for all components (recommended approach)
type ComponentsSchema = {
	button: {
		modifiers: Set<'primary' | 'secondary'> | null;
		elements: {
			icon: {
				modifiers: Set<'small' | 'large'> | null;
			};
			text: {
				modifiers: null;
			};
		};
	};
	input: {
		modifiers: Set<'error' | 'success'> | null;
		elements: {
			label: {
				modifiers: Set<'required' | 'disabled'> | null;
			};
			field: {
				modifiers: null;
			};
		};
	};
};

2. Generate class names

generateBemClassNames reads your schemas and returns a strongly typed helper. Invalid combinations immediately trigger TypeScript errors.

// Create a single BEM instance for all components
const bem = generateBemClassNames<ComponentsSchema>();

// Direct usage (always works)
bem('button');
// "button"

bem('button', { primary: true });
// "button button--primary"

bem('button', 'icon', { small: true });
// "button__icon button__icon--small"

// bem('button', { tertiary: true });      // TypeScript error: unknown modifier
// bem('button', 'label');                  // TypeScript error: unknown element

3. Alternative Short Syntax

For components where you work primarily with one block or element, you can use the forBlock() and forElement() methods. These are optional shortcuts for convenience.

Block-bound generators

// Create block-specific generators
const buttonBem = bem.forBlock('button');
const inputBem = bem.forBlock('input');

// Now you can omit the block name!
buttonBem(); // "button"
buttonBem({ primary: true }); // "button button--primary"
buttonBem('icon', { small: true }); // "button__icon button__icon--small"

inputBem({ error: true }); // "input input--error"
inputBem('label', { required: true }); // "input__label input__label--required"

Element-bound generators

// Create element-specific generators
const buttonIconBem = buttonBem.forElement('icon');
const inputLabelBem = inputBem.forElement('label');

// Ultra-short syntax for common elements
buttonIconBem(); // "button__icon"
buttonIconBem({ small: true }); // "button__icon button__icon--small"

inputLabelBem({ required: true }); // "input__label input__label--required"

These methods are particularly useful for repetitive tasks but are not required for general usage.

4. Merge class names

Use uniqueClassNames to combine dynamic class name fragments while removing duplicates and falsy values.

import { uniqueClassNames } from 'typed-bem';

const className = uniqueClassNames(bem('button'), bem('button', 'icon', { small: props.isSmall }), props.className);

5. Generate a SCSS skeleton (optional)

Typed BEM can mirror your schema into an SCSS file. When creating Set values for modifiers, cast array literals with as const so the literal types stay intact.

import { generateBemScssFile } from 'typed-bem/scss';

const componentDefinition: ComponentsSchema = {
	button: {
		modifiers: new Set(['primary', 'secondary'] as const),
		elements: {
			icon: { modifiers: new Set(['small', 'large'] as const) },
			text: { modifiers: null },
		},
	},
	input: {
		modifiers: new Set(['error', 'success'] as const),
		elements: {
			label: { modifiers: new Set(['required', 'disabled'] as const) },
			field: { modifiers: null },
		},
	},
};

generateBemScssFile(componentDefinition, './components');

The generator writes a file named components.scss next to your script:

.button {
	&--primary {
		// Styles for button--primary
	}
	&--secondary {
		// Styles for button--secondary
	}
	&__icon {
		&--small {
			// Styles for button__icon--small
		}
		&--large {
			// Styles for button__icon--large
		}
	}
	&__text {
		// Styles for button__text
	}
}

.input {
	&--error {
		// Styles for input--error
	}
	&--success {
		// Styles for input--success
	}
	&__label {
		&--required {
			// Styles for input__label--required
		}
		&--disabled {
			// Styles for input__label--disabled
		}
	}
	&__field {
		// Styles for input__field
	}
}

Usage Patterns

Central Schema Registration (Recommended)

Instead of creating separate generateBemClassNames instances for each component, register all components in a central schema:

// schema.ts - Central BEM schema
type AppBemSchema = {
	header: {
		/* ... */
	};
	navigation: {
		/* ... */
	};
	button: {
		/* ... */
	};
	input: {
		/* ... */
	};
	modal: {
		/* ... */
	};
	// ... all your components
};

export const bem = generateBemClassNames<AppBemSchema>();
// components/Button.tsx
import { bem } from '../schema';

const buttonBem = bem.forBlock('button');

export function Button({ variant, size, children }) {
  return (
    <button className={buttonBem({ [variant]: true })}>
      {children}
    </button>
  );
}

Migration from Direct Usage

The new API is fully backward compatible. You can migrate incrementally:

// Before (still works)
const oldStyle = bem('button', { primary: true });

// After (more convenient for single-block components)
const buttonBem = bem.forBlock('button');
const newStyle = buttonBem({ primary: true });

Component-Specific Patterns

// For components with frequent element usage
const modalBem = bem.forBlock('modal');
const modalHeaderBem = modalBem.forElement('header');
const modalBodyBem = modalBem.forElement('body');
const modalFooterBem = modalBem.forElement('footer');

// Usage in component
<div className={modalBem({ open: isOpen })}>
  <header className={modalHeaderBem()}>Title</header>
  <main className={modalBodyBem()}>Content</main>
  <footer className={modalFooterBem()}>Actions</footer>
</div>

API Reference

generateBemClassNames

declare function generateBemClassNames<B extends BemBlocks<BemSchema>>(): TypedBemFunction<B>;
  • Returns a cached bem function with additional methods.
  • Parameters
    • blockName – a key from your schema.
    • blockModifiersOrElementName – either a partial record of block modifiers or an element name.
    • elementModifiers – (optional) a partial record of element modifiers when targeting an element.

Extended Methods

  • .forBlock(blockName) – Returns a block-bound generator that doesn't require the block name.
  • .forElement(elementName) – Available on block-bound generators, returns an element-bound generator.

Example:

const bem = generateBemClassNames<Schema>();

// Direct usage
bem('button', { primary: true });

// Block-bound usage
const buttonBem = bem.forBlock('button');
buttonBem({ primary: true });

// Element-bound usage
const iconBem = buttonBem.forElement('icon');
iconBem({ small: true });

uniqueClassNames

declare function uniqueClassNames(...chunks: (string | undefined | null | false)[]): string;
  • Flattens the provided arguments into a single class string.
  • Ignores falsy values and empty strings.
  • Removes duplicate class names in the final result.

generateBemScssFile

declare function generateBemScssFile<B extends BemBlocks<BemSchema>>(definition: B, outputPath: string): void;
  • Writes <outputPath>.scss in the current working directory.
  • Accepts the same schema object you use to generate class names.
  • Intended for Node.js environments; import it from typed-bem/scss.

License

Typed BEM is distributed under the MIT License.