Convenciones de nombres en CSS: kebab-case, BEM y más allá

La mayoría de los nombres de clase CSS se escriben en kebab-case y en minúsculas (.site-header, .price-tag) porque así es como CSS nombra sus propias cosas: background-color, font-size, flex-direction. BEM parte de ahí con una estructura fija, block__element--modifier, de modo que una clase como card__title--large te dice a qué componente pertenece, qué parte de él es y qué variante muestra.
A continuación: en qué distingue CSS entre mayúsculas y minúsculas y en qué no, por qué kebab-case se convirtió en el estándar, qué dicen de verdad las reglas de nomenclatura de BEM y cuándo otro enfoque hace innecesario BEM.
Qué distingue entre mayúsculas y minúsculas en CSS y qué no
La sintaxis propia de CSS no distingue entre mayúsculas y minúsculas dentro del rango ASCII, así que COLOR: RED funciona exactamente igual que color: red. Hay tres cosas que funcionan de otra manera:
- Los nombres de clase sí distinguen entre mayúsculas y minúsculas.
.Cardno coincide conclass="card". La única excepción es una página en modo quirks, donde los navegadores comparan los nombres de clase sin distinguir mayúsculas, lo que es un motivo más para empezar cada página con<!DOCTYPE html>. - Las propiedades personalizadas también.
--brand-colory--Brand-Colorson dos variables distintas. - Los nombres de elementos y atributos HTML, no. En un documento HTML,
DIVydivseleccionan los mismos elementos. En XML serían distintos.
La forma más sencilla de no tener que volver a pensar en esto es escribir siempre en minúsculas todos los nombres de clase y las propiedades personalizadas. Para ver cómo tratan las mayúsculas otros lenguajes y sistemas de archivos, consulta nuestra comparativa sobre la distinción entre mayúsculas y minúsculas.
Por qué kebab-case se impuso en los nombres de clase CSS
Nada en CSS obliga a usar kebab-case en los nombres de clase. .cardTitle y .card_title son igual de válidos. Los guiones tienen sentido porque todo lo que rodea a un nombre de clase ya los usa: los nombres de propiedades, valores como space-between y atributos HTML como aria-label y data-user-id. Una clase en camelCase en medio de una hoja de estilos llena de guiones parece una errata.
Donde kebab-case resulta incómodo es en JavaScript, porque los guiones no están permitidos en los identificadores. El navegador resuelve sus propios casos: data-user-id se lee como element.dataset.userId, y background-color pasa a ser element.style.backgroundColor. Los nombres de clase suelen quedarse en cadenas (classList.add("is-open")), así que les da igual.
La excepción son los CSS Modules. Un CSS Module exporta sus nombres de clase a JavaScript como un objeto, así que .card-title hay que escribirlo styles["card-title"], mientras que .cardTitle queda como el más limpio styles.cardTitle. Es el único caso en el que nosotros pasaríamos los nombres de clase a camelCase.
Nomenclatura BEM: bloque, elemento, modificador
BEM (Block, Element, Modifier: bloque, elemento, modificador) nació en Yandex. La página de historia de la metodología sitúa el origen del trabajo en 2005, marca el lanzamiento del framework Lego 2.0 de Yandex, en marzo de 2009, como el comienzo de BEM, y fecha en 2010 el paso al código abierto. Las reglas de nomenclatura son breves:
- Bloque: un componente independiente que tiene sentido por sí mismo, como
.card,.search-formo.menu. - Elemento: una parte de un bloque que no tiene significado fuera de él; se nombra con el bloque, dos guiones bajos y el elemento:
.card__title,.menu__item. - Modificador: una marca que cambia el aspecto, el comportamiento o el estado; se nombra con el bloque o el elemento, dos guiones y el modificador:
.card--featured,.card__button--disabled. Un modificador también puede ser un par clave-valor:.form--theme-xmas.
Dentro de cada parte, las palabras se separan con un solo guion, como en .search-form__submit-button. Aquí tienes un componente completo:
<article class="card card--featured">
<h2 class="card__title">Spring sale</h2>
<p class="card__body">Everything 20% off this week.</p>
<a class="card__button card__button--disabled" href="/sale">Shop now</a>
</article>.card { border: 1px solid #ddd; padding: 1rem; }
.card--featured { border-color: gold; }
.card__title { font-size: 1.25rem; }
.card__button--disabled { opacity: 0.5; pointer-events: none; }BEM clásico frente al estilo de dos guiones
Si lees el sitio oficial de la metodología, bem.info, verás que los modificadores tienen otra pinta. Su esquema clásico separa los modificadores con un solo guion bajo: menu_hidden, menu__item_visible, menu__item_type_radio. La versión con -- aparece allí como alternativa, el «Two Dashes style», y es la que documenta getbem.com. Las dos son BEM legítimo; simplemente no las mezcles en el mismo código. bem.info señala además un pequeño inconveniente de los guiones: un guion doble dentro de un comentario HTML puede hacer que falle la validación.
Reglas de BEM en las que es fácil equivocarse
- No hay elementos de elementos. Un título dentro de la cabecera de una tarjeta sigue siendo
card__title, nocard__header__title. En el HTML puedes anidar elementos tan hondo como quieras; los nombres siguen siendo planos. El razonamiento de bem.info: con nombres encadenados, no puedes mover, añadir ni quitar un elemento sin cambiar código existente. - Los modificadores nunca van solos. Escribe
class="button button--primary", noclass="button--primary". El modificador solo se suma a la clase base. - Usa selectores de una sola clase.
.card__title, no.card .card__titlenih2.card__title. La excepción aceptada es un modificador de bloque que cambia sus elementos:.card--featured .card__title. - Nombra los modificadores según su propósito. Esta es una opinión nuestra, no una regla de BEM:
--featuredo--errorsobreviven a un rediseño, mientras que--yellowse convierte en mentira el día que cambia el color de la marca. - Mantén los ganchos de JavaScript fuera de tus clases de estilo. También es opinión nuestra: en los scripts, selecciona los elementos con un atributo data como
data-action="close"en lugar de.card__button, para que renombrar una clase por motivos de estilo no pueda romper el comportamiento.
Renombrar un bloque más adelante es, sobre todo, cuestión de buscar y reemplazar. Pega una plantilla en nuestra herramienta Buscar y reemplazar, activa el modo Regex y reemplaza card(__|--) por product-card$1: todas las clases de elemento y de modificador pasan al nuevo nombre de bloque, y el recuento de coincidencias te dice cuántas han cambiado. Renombra aparte la clase card sola, para no arrastrar palabras que no tienen nada que ver.
Escribir BEM con Sass y con el anidamiento nativo de CSS
El selector padre de Sass puede añadir un sufijo al selector exterior, y su documentación usa BEM como ejemplo. El anidamiento nativo de CSS no puede hacerlo, y MDN lo advierte de forma explícita: un &__title anidado se interpreta como un selector de tipo, así que no produce .card__title.
// Sass: works
.card {
&__title { font-size: 1.25rem; } // compiles to .card__title
&--featured { border-color: gold; } // compiles to .card--featured
}/* Native CSS nesting: write the full class names */
.card {
border: 1px solid #ddd;
&.card--featured { border-color: gold; } /* .card.card--featured */
}
.card__title { font-size: 1.25rem; }Más allá de BEM: cuándo no lo necesitas
BEM existe para evitar que los nombres de clase choquen entre sí y para hacer visible la estructura en una hoja de estilos global. Si otra cosa resuelve el problema de las colisiones, sus nombres largos aportan menos:
- Los CSS Modules limitan por defecto el ámbito de los nombres de clase, así que un simple
.titleen un componente no puede chocar con el.titlede otro. - El CSS utility-first, como Tailwind, sustituye en gran parte los nombres de clase propios por utilidades predefinidas como
font-bold, así que la tarea de nombrar pasa a tus componentes. - Los sitios pequeños, con un puñado de componentes, a menudo se apañan bien con kebab-case simple y un prefijo por componente.
Aun así, kebab-case sigue siendo la opción segura para cualquier nombre de clase que sí escribas.
Reglas prácticas
- kebab-case en minúsculas para nombres de clase y propiedades personalizadas, siempre.
- BEM cuando mucha gente comparte una misma hoja de estilos global:
block__element--modifier, nombres de elemento planos y modificadores junto a la clase base. - Nombres de clase en camelCase solo cuando una herramienta como CSS Modules los expone a JavaScript.
¿Vas a convertir el nombre de un componente en una clase? Escribe «Product Card» en nuestro conversor de mayúsculas y minúsculas y el botón kebab-case te dará product-card. Para las convenciones de todas las demás capas de un proyecto, desde las variables de entorno hasta las URL, consulta nuestra guía rápida de estilos de nombres para desarrolladores.


