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."