
BEMoji is a fully featured BEM framework that uses emoji as class names. It's stupid. It works. It's on npm.
I got into one too many Tailwind vs BEM arguments this month and decided the correct response was to build something that makes both sides equally uncomfortable.
Meet BEMoji: a production-grade CSS framework where every class name is an emoji.
<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>
That is valid, production HTML. It works in every modern browser. I am not sorry.
The core BEM pattern is block__element--modifier. BEMoji keeps that structure exactly, just swapping string names for emoji tokens. The double-underscore and double-hyphen delimiters are preserved, so the class names are still machine-parseable even if they're completely illegible to humans.
It started as a joke and then I accidentally made it real.
This is the part that got out of hand. BEMoji ships with:
--📏-4, --🌑-md, --⭕-full)bem() helperinit, compile, audit, decode, encode and export commandsThe CLI's decode command is probably my favourite part:
npx bemoji decode "🃏__🖼️--🌟"
# card__image--featured
You don't have to write emoji by hand in your source files. The PostCSS plugin lets you use a bracket shorthand:
.[card] {
border-radius: var(--⭕-md);
box-shadow: var(--🌑-sm);
}
.[card__image--featured] {
outline: 2px solid var(--🟡);
}
Which compiles to:
.🃏 {
border-radius: var(--⭕-md);
box-shadow: var(--🌑-sm);
}
.🃏__🖼️--🌟 {
outline: 2px solid var(--🟡);
}
So the output is emoji soup but your source stays readable. Or as readable as it was before, anyway.
import { useBem } from 'bemoji/react';
function Card({ featured }) {
const b = useBem('card');
return (
<article className={b()}>
<div className={b('image', { featured })}>
...
</div>
</article>
);
}
// When featured=true, className becomes "🃏__🖼️ 🃏__🖼️--🌟"
I feel compelled to make the honest case, because it genuinely surprised me:
Free obfuscation. Your production CSS is meaningless to anyone without the config file. You get the obfuscation benefits of CSS Modules without the build complexity.
Enforced vocabulary. Every UI concept maps to one canonical emoji. The config file is your design system contract. Changing a concept's emoji is a one-line config change that propagates everywhere.
It's faster to type than you think. Once you have emoji picker shortcuts set up, 🃏 is two keystrokes. .card__image--featured is 24. The maths eventually works out.
None of this makes it a good idea. But they are real arguments.
// bemoji.config.js
export default {
blocks: {
card: '🃏',
navbar: '🧭',
modal: '🪟',
alert: '🔔',
},
elements: {
image: '🖼️',
title: '🔠',
body: '📝',
button: '🔘',
},
modifiers: {
primary: '🌟',
danger: '🔴',
disabled: '👻',
loading: '⏳',
},
};
npm install bemoji
npx bemoji init
The init command scaffolds a config, imports the base CSS, and wires up PostCSS.
My personal favourite token is 🦶 for footer. If you find a better use for it than I have, open a PR.
gemmaI ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...
communityHey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...
ai(yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...
aiMy laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...
githubactionsI Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...
aiI've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...
Workflows from the Neura Market marketplace related to this DeepSeek resource