BEM, but every class name is an emoji.
BEMoji is a production-grade CSS framework that implements the BEM (Block–Element–Modifier) methodology using emoji as class names. It ships with a full design token system, 24 pre-built components, responsive utilities, and a complete build toolchain.
<!-- This is valid, production HTML -->
<article class="🃏">
<div class="🃏__🖼️ 🃏__🖼️--🌟">
<img src="hero.jpg" alt="...">
</div>
<div class="🃏__📝">
<h2 class="🃏__🔠">Card Title</h2>
</div>
<footer class="🃏__🦶">
<button class="🔘 🔘--🌟">Primary</button>
<button class="🔘 🔘--👻">Disabled</button>
</footer>
</article>| BEMoji | Tailwind | BEM | CSS Modules | |
|---|---|---|---|---|
| Zero runtime JS | ✓ | ✓ | ✓ | ✓ |
| Automatic obfuscation | ✓ | ✗ | ✗ | build-time |
| Semantic naming | ✓ | ✗ | ✓ | ✓ |
| Design token system | ✓ | ✓ | ✗ | ✗ |
| Pre-built components | ✓ | ✗ | ✗ | ✗ |
| Colleague confusion | Maximum | Moderate | Minimal | Minimal |
Genuine reasons to use BEMoji:
- Free obfuscation — Emoji class names are meaningless to scrapers and competitors without your config file. You get CSS-modules-style obfuscation without the build complexity.
- Enforced vocabulary — Every UI concept maps to one canonical emoji. The config file is your design system contract.
- Faster to type —
🃏is two keystrokes with an emoji picker..card__image--featuredis 24. - It actually works — Modern browsers handle emoji in CSS selectors natively. The compiler handles escaped unicode fallbacks for older environments.
npm install bemoji
# or
yarn add bemoji
# or
pnpm add bemojinpx bemoji initThis scaffolds a bemoji.config.js, imports the base CSS, and wires up PostCSS.
🃏 __ 🖼️ -- 🌟
│ │ └─ Modifier (state or variant)
│ └───────── Element (part of the block)
└───────────────── Block (standalone component)
Delimiters are identical to BEM: __ for elements, -- for modifiers. The framework is machine-parseable even if it isn't human-readable.
A standalone, reusable component. Carries no inherited context.
.🃏 /* card */
.🧭 /* navbar */
.📋 /* form */A part of a block. Always expressed as block__element. Cannot exist outside its block.
.🃏__🖼️ /* card image */
.🃏__🔠 /* card title */
.🃏__📝 /* card body */A flag that changes appearance or behavior. Applied alongside the base class.
.🔘--🌟 /* button primary */
.🔘--🔴 /* button danger */
.🔘--👻 /* button disabled */BEMoji defines 143 reserved tokens. Here are the core ones:
| Emoji | Name | Aliases |
|---|---|---|
| 🃏 | card | panel, tile |
| 🧭 | navbar | nav, header |
| 🦶 | footer | — |
| 📋 | form | — |
| 🪟 | modal | dialog, overlay |
| 🔔 | alert | notification |
| 🏷️ | badge | tag, chip |
| 💬 | tooltip | popover |
| 📊 | table | — |
| 📑 | tabs | — |
| 🎠 | carousel | slider |
| 🍞 | breadcrumb | — |
| Emoji | Name | Aliases |
|---|---|---|
| 🖼️ | image | img, media |
| 🔠 | title | heading |
| 📝 | body | content, text |
| 🦶 | footer | actions |
| 🔘 | button | btn, cta |
| 📥 | input | field, control |
| 🔗 | link | anchor |
| 🏷️ | label | — |
| 🎭 | icon | — |
| 📄 | item | row, entry |
| Emoji | Name | Aliases |
|---|---|---|
| 🌟 | primary | featured, hero |
| 🔴 | danger | error, destructive |
| 🟢 | success | ok, valid |
| 🟡 | warning | caution |
| 🔵 | info | — |
| 👻 | disabled | ghost, muted |
| ✅ | active | selected |
| ⏳ | loading | pending, busy |
| 🔒 | locked | readonly |
| 💎 | premium | pro |
| 🆕 | new | fresh |
| Emoji | Name |
|---|---|
| 🔬 | xs |
| 🤏 | sm |
| ⚖️ | md |
| 🏋️ | lg |
| 🏔️ | xl |
| 🌍 | 2xl |
BEMoji's token system uses emoji as CSS custom property names — the obfuscation extends all the way to your design tokens.
:root {
/* Colors */
--⬛: #0d0d0f;
--⬜: #ffffff;
--🔴: #dc2626;
--🟢: #16a34a;
--🟡: #d97706;
--🔵: #2563eb;
--🟣: #7c3aed;
/* Spacing scale */
--📏-1: 0.25rem; /* 4px */
--📏-2: 0.5rem; /* 8px */
--📏-4: 1rem; /* 16px */
--📏-6: 1.5rem; /* 24px */
--📏-8: 2rem; /* 32px */
--📏-12: 3rem; /* 48px */
--📏-16: 4rem; /* 64px */
/* Typography */
--✍️-xs: 0.75rem;
--✍️-sm: 0.875rem;
--✍️-base: 1rem;
--✍️-lg: 1.125rem;
--✍️-xl: 1.25rem;
/* Shadows */
--🌑-sm: 0 1px 3px rgba(0,0,0,.12);
--🌑-md: 0 4px 12px rgba(0,0,0,.15);
--🌑-lg: 0 8px 24px rgba(0,0,0,.2);
/* Border radius */
--⭕-sm: 4px;
--⭕-md: 8px;
--⭕-lg: 16px;
--⭕-full: 9999px;
}Breakpoints use an emoji prefix separated by a zero-width joiner (U+200D):
| Emoji | Breakpoint | Range |
|---|---|---|
| 📱 | xs | 0–639px (base, no prefix) |
| 📟 | sm | 640px+ |
| 📲 | md | 768px+ |
| 💻 | lg | 1024px+ |
| 🖥️ | xl | 1280px+ |
<!-- 1 col mobile, 2 col tablet, 3 col desktop -->
<div class="📐💠 📲📐🔲 💻📐🔳">
...
</div>Write readable BEM names in brackets in your source CSS:
.[card] {
border-radius: var(--⭕-md);
box-shadow: var(--🌑-sm);
background: var(--⬜);
}
.[card__image] {
width: 100%;
aspect-ratio: 16 / 9;
}
.[card__image--featured] {
outline: 2px solid var(--🟡);
outline-offset: -2px;
}The PostCSS plugin compiles this to:
.🃏 { border-radius: var(--⭕-md); box-shadow: var(--🌑-sm); background: var(--⬜); }
.🃏__🖼️ { width: 100%; aspect-ratio: 16 / 9; }
.🃏__🖼️--🌟 { outline: 2px solid var(--🟡); outline-offset: -2px; }import { bem } from 'bemoji/react';
const Card = ({ featured, loading, children }) => (
<article className={bem('card')}>
<div className={bem('card__image', { featured, loading })}>
{children.image}
</div>
<div className={bem('card__body')}>
{children.body}
</div>
</article>
);
// bem('card') → '🃏'
// bem('card__image', { featured: true }) → '🃏__🖼️ 🃏__🖼️--🌟'
// bem('card__image', { loading: true }) → '🃏__🖼️ 🃏__🖼️--⏳'// bemoji.config.js
export default {
version: '1.0',
blocks: {
card: '🃏',
navbar: '🧭',
modal: '🪟',
alert: '🔔',
form: '📋',
table: '📊',
badge: '🏷️',
tooltip: '💬',
tabs: '📑',
carousel: '🎠',
breadcrumb: '🍞',
footer: '🦶',
},
elements: {
image: '🖼️',
title: '🔠',
body: '📝',
footer: '🦶',
button: '🔘',
input: '📥',
link: '🔗',
label: '🏷️',
icon: '🎭',
item: '📄',
divider: '🖇️',
},
modifiers: {
primary: '🌟',
danger: '🔴',
success: '🟢',
warning: '🟡',
info: '🔵',
disabled: '👻',
active: '✅',
loading: '⏳',
locked: '🔒',
premium: '💎',
new: '🆕',
dark: '🕶️',
xs: '🔬',
sm: '🤏',
md: '⚖️',
lg: '🏋️',
xl: '🏔️',
},
breakpoints: {
sm: { prefix: '📟', minWidth: '640px' },
md: { prefix: '📲', minWidth: '768px' },
lg: { prefix: '💻', minWidth: '1024px' },
xl: { prefix: '🖥️', minWidth: '1280px' },
},
separator: {
element: '__',
modifier: '--',
},
compiler: {
escape: 'auto', // 'raw' | 'unicode' | 'auto'
sourceMap: true,
purge: true,
},
};npx bemoji init # Scaffold project
npx bemoji compile # Transform CSS files
npx bemoji audit # Check for unused tokens
npx bemoji decode "🃏__🖼️--🌟" # → card__image--featured
npx bemoji encode "card__image--featured" # → 🃏__🖼️--🌟
npx bemoji export --fmt json # Export token map as JSON
npx bemoji storybook # Generate Storybook stories| Package | Description |
|---|---|
bemoji |
Core framework + CSS |
bemoji-postcss |
PostCSS plugin |
vite-plugin-bemoji |
Vite integration |
bemoji/react |
React bem() helper |
eslint-plugin-bemoji |
ESLint rules |
bemoji-cli |
CLI tools |
VS Code extension — Search "BEMoji" in the Extensions marketplace for IntelliSense, hover tooltips, and autocomplete.
BEMoji targets the same browser support as Baseline 2023. Emoji class names work natively in all modern browsers. The compiler can output unicode-escaped fallbacks (.\01F0CF) for older Webkit environments via the escape: 'unicode' config option.
PRs are welcome. See CONTRIBUTING.md for guidelines.
The most impactful contributions are:
- New emoji token proposals (open an RFC issue first)
- Framework adapter packages (Svelte, Vue, Angular)
- The actual npm packages (this repo contains complete, functional implementations)
MIT © BEMoji Contributors
"It works. We're as surprised as you are."