Abres una hoja de estilos que escribiste hace medio año, cambias un
padding y se descoloca algo en otra página. Miras el selector y es
.container .card .header h3 span. Añades un !important y sigues.
BEM no es una librería ni una herramienta: es una forma de poner nombres que hace que ese
momento no vuelva a pasar.
El problema no es CSS, son los nombres
Este CSS funciona el primer día y es una trampa a partir del segundo:
.card { padding: 1rem; }
.card .title { font-size: 1.5rem; }
.card .title span { color: #888; }
.sidebar .card .title { font-size: 1.2rem; }
.sidebar .card.featured .title { color: red !important; }
Tres problemas de golpe: no sabes dónde se usa cada regla, la especificidad va subiendo sola
(y el !important es la consecuencia, no la causa), y el HTML depende de la
estructura: si mueves ese span, el estilo se pierde.
Bloque, Elemento, Modificador
BEM son tres conceptos y una regla de nombres:
- Bloque: algo independiente y con sentido propio. Se lo llevas a otra
página y sigue funcionando:
card,menu,search-form. - Elemento: una parte del bloque que no tiene sentido fuera de él. Se
escribe con dos guiones bajos:
card__title,menu__item. - Modificador: una variante del bloque o del elemento. Dos guiones:
card--featured,menu__item--active.
bloque__elemento--modificador
Y ya está: no hay más sintaxis que aprender. Lo mismo de antes, con BEM:
<article class="card card--featured">
<h3 class="card__title">
Título
<span class="card__subtitle">subtítulo</span>
</h3>
<p class="card__text">...</p>
</article>
.card { padding: 1rem; }
.card__title { font-size: 1.5rem; }
.card__subtitle { color: #888; }
.card--featured .card__title { color: #b50d87; }
Todos los selectores valen una clase. Ninguno depende de dónde esté el elemento en el DOM. Y leyendo el HTML sabes exactamente qué CSS lo está pintando, sin abrir el inspector.
La ventaja de verdad: especificidad plana
Con BEM casi todos tus selectores tienen la misma especificidad: 0-1-0, una
clase. Cuando dos reglas compiten, gana la que está después en el archivo, que es una
regla que un humano puede seguir. Sin BEM acabas en esta escalera:
.title /* 0-1-0 */
.card .title /* 0-2-0, ya no puedo sobreescribir con una clase */
#main .card .title /* 1-2-0, ahora necesito otro id */
/* ... y aquí es donde aparece el !important */
El !important no es un fallo de disciplina: es lo que queda cuando la
especificidad se te ha ido de las manos. BEM lo evita de raíz.
Un ejemplo de esta misma web
La miga de pan que ves arriba está escrita así:
<nav class="article-breadcrumb">
<a class="article-back" href="../">Blog</a>
<span class="article-breadcrumb__sep">/</span>
<span class="article-breadcrumb__current">BEM: el CSS que...</span>
</nav>
.article-breadcrumb { font-size: 1.05rem; margin-bottom: 3.5rem; }
.article-breadcrumb__sep { margin: 0 .4rem; opacity: .5; }
.article-breadcrumb__current { opacity: .6; }
El separador y el título actual no significan nada fuera de la miga de pan: por eso son
elementos. El enlace article-back, en cambio, se reutiliza al final del
artículo para volver al blog, así que es un bloque por su cuenta. Esa decisión —qué es
elemento y qué es bloque— es el 90 % de aplicar bien BEM.
Las cuatro reglas que evitan los líos
1. Nunca encadenes elementos. Aunque el HTML esté anidado tres niveles, el nombre solo tiene uno:
/* mal */
.card__header__title { }
/* bien */
.card__title { }
El elemento pertenece al bloque, no a su padre. Si el nombre se te hace larguísimo, suele ser señal de que ahí dentro hay un bloque nuevo esperando a salir.
2. Un modificador nunca va solo. Siempre acompaña a su bloque, porque solo aporta la diferencia:
<!-- mal: el botón se queda sin estilos base -->
<button class="button--primary">
<!-- bien -->
<button class="button button--primary">
3. Nombra por función, no por aspecto. button--red envejece
fatal: el día que el rojo pase a naranja tendrás una clase que miente en todo el proyecto.
button--danger sigue siendo verdad.
4. Los bloques se componen, no se anidan por CSS. Si una tarjeta lleva un
botón dentro, el botón sigue siendo un bloque button; y si necesitas colocarlo
de forma especial dentro de la tarjeta, usa las dos clases a la vez:
<button class="button button--primary card__action">Comprar</button>
/* el botón trae su aspecto; la tarjeta solo dice dónde va */
.card__action { margin-top: auto; align-self: flex-start; }
A eso se le llama mix: aspecto y posición separados. Es lo que permite mover el botón a otra tarjeta sin arrastrar estilos.
Con Sass, sin perder la búsqueda
Si usas un preprocesador, el anidado ahorra escritura, pero cuidado con esto:
.card {
padding: 1rem;
&__title { font-size: 1.5rem; } // genera .card__title
&--featured { border: 2px solid; } // genera .card--featured
}
El resultado es correcto, pero acabas de perder algo valioso: buscar
card__title en el proyecto ya no encuentra nada, porque ese texto no existe en
ningún archivo. En equipos grandes suele compensar escribir el nombre completo aunque sea
más largo.
Cuándo BEM no es la respuesta
BEM brilla en componentes de interfaz que se repiten. No lo necesitas para todo:
- Para utilidades (
.mt-3,.text-center) o si ya usas Tailwind, BEM no aporta nada. - Dentro de un componente Vue con estilos scoped el aislamiento ya te lo da el framework; aun así, los nombres BEM siguen ayudando a leerlo.
- Para contenido editorial (un artículo, un texto de un CMS) tiene más sentido estilar por etiqueta dentro de un bloque contenedor, porque el HTML no lo controlas tú.
Lo importante no es la sintaxis: es que el nombre de la clase diga qué es la cosa y a qué pertenece. Si eliges otra convención y la aplicas con la misma disciplina, el resultado es igual de bueno.
Lo que se rompe si no lo sabes
- Elementos que en realidad son bloques. Si
card__buttonempieza a aparecer también fuera de la tarjeta, ha dejado de ser un elemento. Sácalo. - Modificadores que cambian demasiado. Si
card--compactredefine media tarjeta, probablemente sean dos bloques distintos. - Bajar a selectores de etiqueta «solo por esta vez».
.card h3vuelve a atarte a la estructura y rompe la especificidad plana. - BEM a medias es peor que no usarlo. Si media hoja va con BEM y la otra media con selectores anidados, nadie sabe qué convención sigue cada regla.
- Nombres larguísimos como excusa.
product-card__footer-actions--disabledno es un problema de BEM, es un aviso de que ese bloque hace demasiadas cosas.
Y ya está
Un bloque por componente, elementos con __ para sus partes, modificadores con
-- para las variantes y una sola clase por selector. No hace falta instalar
nada ni cambiar de herramientas: solo ponerse de acuerdo en los nombres. La recompensa
llega dentro de seis meses, cuando abras el archivo y lo entiendas a la primera.