BEM Conventions
BEM — Block, Element, Modifier — is the naming convention CaterCow uses for CSS classes across all apps. It keeps class names predictable, prevents style leakage between components, and makes the relationship between HTML structure and CSS immediately readable. There are many resources out there on BEM Methodology, and this is only meant to serve as a brief overview of how CaterCow puts it to use.
The Three Parts
Block — a standalone component. The root class of a Vue component is always its block.
.admin-context { ... }Element — a part of a block, connected with __.
.admin-context__trigger { ... }
.admin-context__content { ... }Modifier — a variant or state, connected with --.
.package-card--loading { ... }
.order-status--pending { ... }How CaterCow Writes BEM
All component styles use <style lang="scss" scoped> with SCSS & nesting to keep BEM structure readable without repeating the block name.
Here's an example from AdminContextMenu.vue:
<template>
<div v-if="auth.adminUser" class="admin-context">
<button class="admin-context__trigger" @click="toggleModal">Admin</button>
<Modal v-if="showContext" @close="toggleModal">
<template #content>
<div class="admin-context__content">
<slot></slot>
</div>
</template>
</Modal>
</div>
</template>
<style lang="scss" scoped>
.admin-context {
&__trigger {
@include font-size(p);
position: fixed;
bottom: 10px;
left: 10px;
padding: 1rem;
color: $white;
background: $green;
}
&__content {
h5 { line-height: 0; }
}
}
</style>Block Names Match the Component
The block name is always loosely the kebab-case equivalent of the Vue component filename.
| Component file | Block class |
|---|---|
AdminContextMenu.vue | .admin-context-menu |
PackageReviewItem.vue | .package-review-item |
ManagedPlacePage.vue | .managed-place-page |
This makes it trivial to find the source styles for any class name in the DOM.
Modifiers in Vue Templates
Modifiers are applied dynamically in Vue using :class binding. The base class is always included alongside the modifier.
<template>
<span :class="['order-status', `order-status--${order.status}`]">
{{ order.status }}
</span>
</template>
<style lang="scss" scoped>
.order-status {
@include status-colors; // generates --pending, --accepted, --canceled etc.
font-weight: bold;
}
</style>Never apply a modifier without the base class. order-status--pending alone is not valid — it augments order-status, it doesn't replace it.
Blocks Don't Set Their Own Margins
A block should define how it looks internally, but never how it sits in the page. Don't set margin, position, top, left, or float on the block's root class — let the parent or layout element control placement. All rules are meant to be broken and if a usecase calls for breaking this rule, that's ok.
// Avoid: block positioning itself
.info-card {
margin-bottom: 2rem;
float: left;
}
// Preferred: parent controls spacing
.card-grid {
@include grid(repeat(3, 1fr), 1fr);
}
.info-card { ... } // info-card defines only its internal appearanceThis keeps blocks reusable — the same .info-card can be dropped into any layout without carrying unwanted spacing baggage.
Nested Elements
Strict BEM keeps element names flat — block__element, never block__element__sub-element. In practice, CaterCow uses one level of sub-element nesting in some components (e.g., package-review-item__header__name). This is acceptable when the nesting reflects a clear visual hierarchy, but keep it to a maximum of two levels. Beyond that, flatten the names even if it doesn't mirror the DOM tree exactly.
// Acceptable: one level of sub-element
.package-review-item {
&__header {
&__name { font-weight: bold; }
&__date { color: $dark-grey; }
}
}
// Avoid: three levels is too deep
.package-review-item {
&__header {
&__meta {
&__icon { ... } // Avoid: flatten to __header__icon or __icon
}
}
}