@morev/bem-vue

Compile-time BEM directive and helpers for Vue components.
354
Install
npm install @morev/bem-vue
Latest Version:0.1.2
Vue:3
License:MIT
Last Updated:Oct 16, 2026
Links: npm
Maintainer: morev

@morev/bem-vue

Compile-time BEM directive and helpers for Vue, powered by @morev/bem-classnames.

The adapters replace v-bem with an ordinary :class expression while Vue compiles the template. There is no runtime directive or global mixin to register, and components that do not use v-bem or useBem do not load the helper runtime.

Installation

pnpm add @morev/bem-vue

Add the client declarations to tsconfig.json so Vue and vue-tsc recognize the directive and the block component option:

{
  "compilerOptions": {
    "types": ["@morev/bem-vue/client"]
  }
}

Vite

Place the BEM adapter after the Vue plugin:

import vue from '@vitejs/plugin-vue';
import bemVue from '@morev/bem-vue/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [vue(), bemVue()],
});

The adapter supports Vue 3.5 VDOM compilation and Vue 3.6 Vapor through the Vue plugin's Vapor mode:

vue({
  features: { vapor: true },
});

Vapor support includes client builds and SSR. A Vapor SFC may use a string literal or a top-level/imported identifier for defineOptions({ block, name }); expressions with repeated evaluation or side effects are rejected.

Webpack

Place bemVue() before VueLoaderPlugin. The adapter must update the static vue-loader rule before VueLoaderPlugin normalizes it:

import { VueLoaderPlugin } from 'vue-loader';
import bemVue from '@morev/bem-vue/webpack';

export default {
  module: {
    rules: [
      {
        test: /\.vue$/u,
        loader: 'vue-loader',
      },
    ],
  },
  plugins: [bemVue(), new VueLoaderPlugin()],
};

Webpack supports the Vue 3 VDOM pipeline. Vapor is intentionally limited to Vite because vue-loader does not expose the corresponding compiler pipeline.

Directive syntax

Use v-bem for the block and an argument for an element:

<script setup lang="ts">
const active = true;
const element = 'title';
const mix = 'layout__item';
</script>

<template>
  <article v-bem>
    <h2 v-bem:title="{ active }">Title</h2>
    <div v-bem:[element]="[{ active }, mix, 'static-mix']" />
  </article>
</template>

Values follow the recursive BemArgument contract: modifier objects, mixin strings, nullish values, or nested arrays of those values. A nullish dynamic element means the block itself.

Directive modifiers are not supported. Replace the legacy modifier syntax with ordinary values:

<!-- Invalid -->
<div v-bem.static.[mix] />

<!-- Valid -->
<div v-bem="['static', mix]" />

The transform keeps the directive's original position among class, :class, and v-bind props, leaving Vue responsible for its normal class merging and component fallthrough behavior.

Block names and useBem

The default block is the .vue filename without its extension. Use the fixed block component option when the Vue component name and BEM block differ:

<script setup lang="ts">
import { useBem } from 'virtual:@morev/bem-vue';

defineOptions({
  name: 'the-component-item',
  block: 'the-component',
});

const b = useBem();
const foreign = useBem('foreign-block');
</script>

The lookup order is an explicit useBem(name) argument, block, the explicit Vue component name, the filename resolver, and Vue's generated __name as a last fallback. An explicit name works outside a component. Calling useBem() without any available name throws a descriptive runtime error.

Calls imported directly into an SFC receive the filename fallback at compile time. A call in an external composable should pass a block explicitly when it cannot rely on the current component's block or name.

Options

Both adapters accept the same options:

bemVue({
  factoryOptions: {
    namespace: 'ui-',
  },
  resolveBlockName: ({ filename, root }) => resolveProjectName({ filename, root }),
});

factoryOptions is passed to bemClassnames() and must be safely serializable as JSON. resolveBlockName changes the project-wide filename fallback.

For custom integrations, @morev/bem-vue/compiler exports the stateful compiler core with SFC transform, Vue template transform, and virtual-module hooks.

Boundaries

  • Vue 3 only; there is no Vue 2 or Nuxt-specific module.
  • VDOM works with Vite and Webpack 5; Vapor works with Vite.
  • Rollup and esbuild adapters are not provided because they do not own a Vue template compiler pipeline.
  • v-bem is rejected on <template> and <slot>, and an element may contain only one v-bem directive.

Related Packages

@rhapsodic/bem-classnames-vue

Vue 3 composables for @rhapsodic/bem-classnames

1,612 0
@kungal/ui-core

KunUI framework-agnostic core — design types, cn(), variant matrix, radius syste...

15,516 47
vue-bem-cn

BEM class name generator for Vue.JS

8,290 125
vue3-bem

Simple implementation of BEM in Vue 3.x

11,555 6
rizzo-css

A modern CSS design system with semantic theming, 14 themes, accessible componen...

11,599 1

Version History

Version Released Vue License
0.1.2 ^3.5.0 || >=3.6.0-rc.4 <4 MIT
0.1.1 ^3.5.0 || >=3.6.0-rc.4 <4 MIT
0.1.0 ^3.5.0 || >=3.6.0-rc.4 <4 MIT