For three years I wrote CSS exclusively in BEM. .card__title--featured, that whole world. It worked, but my stylesheets kept getting bigger and the names kept getting longer. When I found CUBE CSS in 2023, something clicked. Here is why I switched and how the four parts fit together.
01The problem with BEM-only
BEM (Block, Element, Modifier) is a good naming convention. It is not a complete architecture. With BEM alone, I kept hitting three problems:
- Layout was always inside a block. So generic page layouts got named
.card__gridand were not reusable across other blocks. - Utility classes felt dirty. If I wanted a quick margin-top, I would write a one-off modifier — when a
.mt-4would have been clearer. - Edge cases multiplied. Every special case became another modifier, then a modifier of a modifier.
CUBE CSS, by Andy Bell, names these forces explicitly and lets each do its job.
02What CUBE stands for
CUBE = Composition · Utility · Block · Exception. The order matters: it is the order in which styles get layered onto an element. Each layer answers a different question.
“Group your styles by what they do, not by which component they live in.”— the whole shift in one line
03C — Composition
Composition handles layout. Spacing, grids, flow — the macro arrangement of stuff on a page. Composition classes are typically used by themselves, on a parent. They do not care what is inside.
/* a stack with vertical rhythm */
.stack > * + * {
margin-top: var(--space);
}
/* a centered, max-width container */
.center {
max-width: var(--measure);
margin-inline: auto;
}
04U — Utility
Utility classes do one thing each. Margin. Font size. Background color. Like Tailwind, but bespoke to your design tokens.
.text-mute { color: var(--ink-mute); }
.mt-4 { margin-top: 1rem; }
.flex-row { display: flex; gap: .5rem; }
05B — Block
Blocks are components: cards, buttons, navs. They have their own visual identity that is not just composition + utility.
.btn {
padding: .75rem 1.5rem;
border: 2px solid var(--ink);
border-radius: 999px;
background: var(--cobalt);
color: white;
}
06E — Exception
Sometimes a block needs a one-off variation. Exception is the right layer for that — applied with data-* attributes (not modifier classes):
.btn[data-variant="ghost"] {
background: transparent;
color: var(--ink);
}
Using data-* instead of .btn--ghost keeps your HTML semantic and signals “this is a state, not a sibling component.”
07How to try it on a project
- On your next project, start with a tiny utility set (8–12 classes). Spacing, color, layout.
- Build 2–3 composition classes for the macro layout (stack, cluster, sidebar, grid).
- Build blocks only when a chunk of UI clearly has its own identity.
- Reach for exceptions sparingly — they are the safety valve, not the default.
The mindset shift takes about a week. The maintenance payoff lasts the life of the project.