wandres.dev
ARQUITECTURA I · De BEM a los tokens de diseño

BEM: el nombre como sustituto del ámbito

Qué garantizaba BEM de verdad, por qué su fealdad era funcional, y qué queda de él cuando el lenguaje ya tiene @scope, :where() y anidamiento nativo.

⏱ 18 min

.tarjeta__titulo--destacado es probablemente el fragmento de CSS más ridiculizado de la historia, y también uno de los más eficaces que se han escrito nunca. BEM resolvía tres problemas a la vez con una sola idea —meter la jerarquía en el nombre en lugar de en el selector— y lo hacía con una garantía que ninguna metodología anterior daba: si todo el mundo lo respeta, dos reglas de componentes distintos no pueden colisionar jamás. La pregunta de 2026 no es si BEM era bueno. Es qué parte de lo que garantizaba lo garantiza ahora el lenguaje.

🎯 Al terminar esta lección sabrás
  • Enunciar las tres garantías que ofrecía BEM y de dónde salía cada una.
  • Explicar por qué la especificidad plana era el objetivo y no un efecto secundario.
  • Comparar BEM con @scope y con el anidamiento nativo en términos de garantías, no de estética.
  • Decidir con criterio si mantener BEM en un proyecto de 2026.

Las tres garantías

BEM —bloque, elemento, modificador, salido de Yandex alrededor de 2010— se resume en una regla de nombres: bloque__elemento--modificador, y una sola clase por selector. De ahí salen tres garantías, y merece la pena separarlas porque hoy se cumplen con mecanismos distintos.

Garantía de no colisión. Si el nombre del bloque es único en el proyecto, ninguna regla de un componente puede alcanzar a un elemento de otro. .tarjeta__titulo no toca nada dentro de .modal, pase lo que pase con el marcado. Esto es ámbito, implementado con una convención de nombres en lugar de con una función del lenguaje. Es la garantía más importante de las tres.

Garantía de especificidad plana. Como cada selector es una sola clase, todos los selectores tienen la misma especificidad. Y cuando toda la hoja tiene la misma especificidad, la cascada se reduce al orden del documento, que es una regla que un humano puede simular en su cabeza. Eso corta de raíz la escalada descrita en la lección anterior: no hay guerra de especificidad si nadie tiene armas.

Garantía de localización. Al ver class="tarjeta__pie" en el HTML sabes, sin abrir nada, que la regla está en el fichero de la tarjeta y que se llama exactamente así. Buscar la definición es un grep exacto, no una investigación. En una hoja de veinte mil líneas eso no es comodidad: es la diferencia entre poder tocarla y no poder.

La fealdad del nombre es el precio de las tres, y era un precio consciente. Un nombre largo y redundante es un nombre que no colisiona, que no necesita contexto para entenderse y que se puede buscar sin falsos positivos.

ℹ️
Por qué prohibía anidar

La regla más contraintuitiva de BEM es que .tarjeta .tarjeta__titulo está prohibido aunque parezca más legible. El motivo es que ese selector tiene especificidad de dos clases y rompe la segunda garantía: en cuanto una regla vale más que otra, vuelve a existir la escalada. La disciplina solo funciona si es absoluta; una sola excepción reintroduce el problema entero para todo el equipo.

Lo que BEM no resolvía

Conviene ser justo también en la otra dirección, porque la nostalgia tiende a limpiar los defectos.

BEM no resolvía el acoplamiento al contexto. Podías escribir .tarjeta--en-barra-lateral y el componente volvía a saber dónde estaba, exactamente el pecado que OOCSS señalaba. BEM no lo prohibía; simplemente le daba un nombre bonito.

No resolvía la duplicación de valores. Sin custom properties, el color de marca aparecía literalmente en trescientos sitios, o venía de una variable de Sass que se resolvía en compilación y por tanto no se podía cambiar en runtime ni sobrescribir en un subárbol.

No resolvía el coste de escritura del HTML. class="tarjeta tarjeta--destacada tarjeta__cuerpo" es mucho texto, y en plantillas generadas se vuelve difícil de leer. La respuesta habitual era generar los nombres desde Sass con &__elemento, lo que introducía un problema nuevo: los nombres completos dejaban de existir en el código fuente y grep dejaba de encontrarlos, matando la tercera garantía. Es un ejemplo perfecto de herramienta que rompe lo que la metodología protegía.

Y no resolvía el código muerto. Un .tarjeta__pie sigue en el fichero aunque la plantilla haya dejado de usarlo, y saberlo sigue exigiendo analizar el HTML generado.

Las mismas garantías, con mecanismos

Cada una de las tres garantías tiene hoy un mecanismo del lenguaje que la ofrece mejor.

No colisión se llama @scope. Acota un bloque de reglas a un subárbol, con techo —el ámbito— y opcionalmente suelo —dónde deja de aplicarse—. El motor lo hace cumplir: no depende de que nadie recuerde el prefijo.

@scope (.tarjeta) to (.tarjeta .tarjeta) {
  h3 { font-size: 1.25rem; }
  footer { color: var(--texto-tenue); }
}

Fíjate en lo que desaparece: los nombres. Dentro del ámbito puedes usar selectores de elemento, que era la primera cosa que BEM te prohibía. Y el límite inferior resuelve el caso que BEM ni siquiera consideraba: una tarjeta anidada dentro de otra tarjeta ya no hereda las reglas de la de fuera.

Especificidad plana se llama :where(). En lugar de imponer un selector de una sola clase, envuelves lo que quieras y su especificidad pasa a ser cero.

:where(.tarjeta) :where(h3) { margin-block: 0; }

Es más potente que la regla de BEM, porque no te obliga a renunciar a los selectores expresivos para conservar el control de la cascada. Y @layer va más lejos todavía: permite que una capa entera pierda contra otra independientemente de la especificidad de nadie, que es algo que BEM no podía expresar de ninguna forma.

Localización es la única que BEM sigue haciendo mejor, y hay que reconocerlo. Con @scope y selectores de elemento, ver <h3> en el HTML no te dice dónde está la regla que lo estila. Recuperas la capacidad de búsqueda de otra manera —los módulos de CSS y las herramientas modernas la dan por colocación de ficheros— pero si tu proyecto no tiene ninguna de esas cosas, el nombre explícito sigue siendo la única pista.

El anidamiento nativo, por su parte, sustituye a la generación de nombres de Sass sin su defecto:

.tarjeta {
  padding: 1.25rem;

  & > h3 { font-size: 1.25rem; }
  &:has(img) { padding-block-start: 0; }
}

Aquí no se generan nombres nuevos, así que no hay nada que dejar de encontrar con grep. Pero cuidado: el anidamiento sí suma especificidad, y usarlo sin cuidado reintroduce la escalada. Anidar no es BEM; es lo contrario de BEM salvo que lo envuelvas en :where().

¿Mantener BEM en 2026?

La respuesta honesta depende de tres cosas, y ninguna es estética.

Si el proyecto ya está en BEM y funciona, no lo migres. No hay ningún beneficio que compense reescribir una hoja de estilos que no da problemas. El coste de una migración de nombres es enorme y el riesgo de regresión visual es alto porque no hay tests que lo cubran.

Si empiezas de cero y tienes componentes con colocación de ficheros —cada componente con su CSS al lado, o módulos de CSS, o cualquier cosa que genere nombres únicos por ti—, BEM no aporta nada. Las tres garantías las tienes por construcción y los nombres largos son solo ruido.

Si empiezas de cero sin herramientas de build, o escribes una biblioteca cuyo CSS aterriza en páginas que no controlas, un prefijo por biblioteca sigue siendo la defensa más barata que existe. No hace falta BEM entero; hace falta que tus nombres no colisionen con los de nadie. @scope no te salva ahí, porque el ámbito depende de un selector raíz que también puede colisionar.

BEM no era una convención de nombres: era un sistema de tipos escrito a mano

La forma más útil de entender BEM, y la que explica por qué funcionaba tan bien pese a ser tan feo, es dejar de verlo como estética y verlo como verificación. Un nombre BEM codifica en la cadena de texto tres piezas de información que el lenguaje no tenía dónde guardar: a qué componente pertenece esta regla, qué papel juega dentro de él, y qué variación es. Con eso, un humano leyendo el HTML puede verificar localmente si una clase está bien puesta —.modal__titulo dentro de .tarjeta es visiblemente un error— sin ejecutar nada y sin consultar el CSS. Eso es exactamente lo que hace un sistema de tipos: mover información del tiempo de ejecución al tiempo de lectura para que los errores sean visibles antes de que ocurran. Y explica las dos cosas que más se le critican. La verbosidad es intrínseca: un tipo escrito a mano en una cadena de texto no puede ser corto, porque no hay inferencia. Y la fragilidad también: como el compilador no participa, la verificación depende de que una persona se fije, que es la peor forma de comprobar cualquier cosa. Cuando llegan @scope, los módulos de CSS o cualquier sistema que genere nombres únicos, lo que ocurre no es que BEM pase de moda: es que el sistema de tipos deja de ser manual. Por eso la respuesta correcta a “¿sigo usando BEM?” es siempre la misma pregunta: ¿tienes ya algo que haga cumplir esas garantías por ti? Si lo tienes, BEM es trabajo duplicado. Si no lo tienes, sigue siendo lo mejor que hay.