A simple Vue.js directive to map BEM CSS class names.
Install using your package manager of choice.
yarn add vue-simple-bem
npm i --save vue-simple-bem
You can install the directive as a global dependency. The configuration options are optional.
import Vue from 'vue';
import { bemPlugin } from 'vue-simple-bem';
Vue.use(bemPlugin, {...});
You can also import it directly into a component.
<script>
import { bem } from 'vue-simple-bem';
export default {
name: 'MyComponent',
directives: { bem }
};
</script>
Before continuing, you may want to get familiar with BEM and the problem it's solving.
At it's simplest, it is a css naming convention that keeps your selectors flat and expresses an explicit relationship between classes in a component.
Here is some further reading:
<script>
export default {
name: 'MyComponent',
};
</script>
<template>
<div v-bem>
<div v-bem:sampleText>Example Text</div>
</div>
</template>
- The root
div
will become<div class="my-component">
. The Block name is derived from component name. - The child element
div
will become<div class="my-component__sample-text">
<script>
export default {
name: 'MyComponent',
props: {
bold: {
type: Boolean,
default: true
},
italics: {
type: Boolean,
default: true
},
center: {
type: Boolean,
default: false
}
}
};
</script>
<template>
<div v-bem="{ bold, emphasizeText: italics }">
<div v-bem:sampleText="{ bold, center }">Example Text</div>
</div>
</template>
- The root
div
will become<div class="my-component my-component--bold my-component--emphasize-text">
- The child element
div
will become<div class="my-component__sample-text my-component__sample-text--bold">
If a modifier isn't dynamic then you can use this syntax.
<script>
export default {
name: 'MyComponent'
};
</script>
<template>
<div v-bem.floatLeft>
<div v-bem:sampleText.italics>Example Text</div>
</div>
</template>
- The root
div
will become<div class="my-component my-component--float-left">
- The child element
div
will become<div class="my-component__sample-text my-component__sample-text--italics">
You can use both together as well.
<script>
export default {
name: 'MyComponent',
props: {
bold: {
type: Boolean,
default: true
}
}
};
</script>
<template>
<div v-bem.floatLeft>
<div v-bem:sampleText.italics="{ bold }">Example Text</div>
</div>
</template>
- The root
div
will become<div class="my-component my-component--float-left">
- The child element
div
will become<div class="my-component__sample-text my-component__sample-text--bold my-component__sample-text--italics">
The BEM block will automatically use the name of the component.
If the component does not have a name then it will fallback to using the component's tag that
Vue uses (which is usually what you registered the component as in the parent).
If neither of these are available it uses bem-block
.
The component's name may be either pascal cased or kebab cased.
The name will be converted into kebab casing either way for the CSS class name.
If the component's name is SomeComponent
then the CSS block class name will be some-component
.
If the component's name is another-component
then the CSS block class name will be the same.
If you bind an object with properties to the directive then anything that evaluates as truthy
will be converted to kebab casing and used as a block modifier.
It is also possible to add a block modifier onto a child component's context.
The below example will add the following CSS classes.
my-component
my-component--bold-text
my-component--italics
<script>
export default {
name: 'MyComponent'
};
</script>
<template>
<div v-bem="{ boldText: true, underline: false, italics: true }">
Some Text
</div>
</template>
If a modifier is permanent and does not need to be evaluated then you may use the below syntax. The classes will be the same as above.
<script>
export default {
name: 'MyComponent'
};
</script>
<template>
<div v-bem.boldText.italics>
Some Text
</div>
</template>
You can also mix these two syntaxes together. The result will be the same. The object values inside of the quotations will take precedence.
<script>
export default {
name: 'MyComponent'
};
</script>
<template>
<div v-bem.boldText.italics="{ underline: false }">
Some Text
</div>
</template>
You may add an element CSS class using the below syntax.
The block portion will still use the component's name.
The below example will generate CSS element classes called
my-component__some-elem
and my-component__text
respectively.
<script>
export default {
name: 'MyComponent'
};
</script>
<template>
<div v-bem>
<span v-bem:someElem>Some Text</span>
<span v-bem:text>More Text</span>
</div>
</template>
The syntax remains the same for element modifiers as it does for block modifiers. The below example will generate the following classes.
my-component__some-elem--bold
my-component__some-elem--underline
my-component__text--underline
<script>
export default {
name: 'MyComponent'
};
</script>
<template>
<div v-bem>
<span v-bem:someElem="{ bold: true, underline: true, center: false }">Some Text</span>
<span v-bem:text="{ bold: false, underline: true }">More Text</span>
</div>
</template>
Here is that example again using the alternative syntax if the modifiers do not need to be evaluated.
<script>
export default {
name: 'MyComponent'
};
</script>
<template>
<div v-bem>
<span v-bem:someElem.bold.underline>Some Text</span>
<span v-bem:text.underline>More Text</span>
</div>
</template>
Once again with the combined syntaxes.
<script>
export default {
name: 'MyComponent'
};
</script>
<template>
<div v-bem>
<span v-bem:someElem.bold.underline="{ center: false }">Some Text</span>
<span v-bem:text.underline="{ bold: false }">More Text</span>
</div>
</template>
If you want to add a BEM mod onto a child component's context then you can specify the element
to be self
. The child component's name or tag will be used to determine the block and the mod
will be determined like normal. The example below will generate the following CSS classes for the
child component and add them to the child component CSS class list.
child-component--bold
child-component--underline
<script>
import ChildComponent from './ChildComponent';
export default {
name: 'ParentComponent',
components: { ChildComponent }
};
</script>
<template>
<div v-bem>
<child-component v-bem:self.bold="{ underline: true, strikeout: false }" />
</div>
</template>
default:
'bem'
This changes the name the directive is registered as.
This will change the directive in your template.
The below example shows adding a block after changing the name to 'css'
.
<template>
<div v-css>Example Text</div>
</template>
- Add ability to apply BEM mod to child component
- Add option to manually specify component property that indicates the BEM block instead of using
name
- Add option to configure whether names are kebab cased or not