Arquitectura CSS

Cómo organizar CSS que escala: especificidad, cascada, BEM, metodologías de nombre y estructura de archivos para proyectos reales.

CSS sin estructura funciona hasta cierto punto. Luego empiezan los problemas: estilos que se pisan sin saber por qué, !important por todas partes, miedo a tocar cualquier regla porque algo puede romperse. La arquitectura CSS no es burocracia — es la diferencia entre código que escala y código que se convierte en deuda técnica.


Entender la cascada y la especificidad

La cascada es el algoritmo que decide qué estilo gana cuando hay conflicto. En orden de prioridad:

  1. Importancia!important siempre gana (y siempre es una señal de alarma)
  2. Origen — estilos del autor > estilos del navegador
  3. Especificidad — cuánto de específico es el selector
  4. Orden — si todo lo demás es igual, gana el que aparece último

La especificidad se calcula con tres contadores: (IDs, clases/atributos/pseudoclases, elementos/pseudoelementos):

p                    /* 0,0,1 */
.card                /* 0,1,0 */
#header              /* 1,0,0 */
.card p              /* 0,1,1 */
.card .title         /* 0,2,0 */
#header .nav a:hover /* 1,1,2 */

El problema de la especificidad alta es que para sobreescribirla necesitas especificidad aún más alta, y así escalas hasta !important:

/* ❌ Espiral de especificidad */
#sidebar .widget .widget-title { color: blue; }
#sidebar .widget .widget-title.active { color: red; }
#sidebar .widget .widget-title.active:hover { color: green !important; }

La solución: mantener la especificidad baja desde el principio.


La regla del selector plano

El CSS más mantenible usa selectores de especificidad baja y predecible. El objetivo es que la mayoría de reglas sean de clase única:

/* ❌ Selector anidado profundo — especificidad alta, difícil de sobreescribir */
.sidebar .menu .menu-item .menu-link:hover span { color: red; }

/* ✅ Clase directa — especificidad mínima, fácil de razonar */
.menu-link:hover { color: red; }

Cuanto más anidas, más acoplado está el CSS a la estructura HTML. Si mueves el elemento a otro contenedor, el estilo deja de funcionar.


BEM — Block Element Modifier

BEM es una convención de nomenclatura que hace la relación entre HTML y CSS explícita en el nombre de las clases:

.bloque {}                 /* componente independiente */
.bloque__elemento {}       /* parte interna del bloque (doble guión bajo) */
.bloque--modificador {}    /* variación del bloque (doble guión) */
.bloque__elemento--mod {}  /* variación de un elemento */
/* Componente: .card */
.card {}
.card__image {}      /* imagen dentro de la card */
.card__title {}      /* título dentro de la card */
.card__body {}       /* cuerpo dentro de la card */
.card__footer {}     /* footer dentro de la card */

/* Modificadores del bloque */
.card--featured {}   /* card destacada */
.card--horizontal {} /* card en layout horizontal */

/* Modificadores de elemento */
.card__title--large {}
<article class="card card--featured">
  <img class="card__image" src="..." alt="">
  <div class="card__body">
    <h2 class="card__title card__title--large">Título</h2>
  </div>
  <footer class="card__footer">...</footer>
</article>

Ventajas de BEM:

  • Especificidad siempre 0,1,0 — predecible y fácil de sobreescribir
  • Sabes qué hace cada clase con solo leer el nombre
  • No importa dónde esté el HTML — el estilo no depende de la estructura

No es necesario ser estricto: BEM es una guía, no una religión. Lo importante es la coherencia dentro del proyecto.


Estructura de archivos CSS

Para proyectos medianos y grandes, separar los estilos en archivos por responsabilidad hace el código mucho más navegable:

styles/
  tokens/
    _colors.css        /* paleta de colores */
    _typography.css    /* escala tipográfica */
    _spacing.css       /* escala de espaciado */
    _shadows.css       /* sombras */
  
  base/
    _reset.css         /* reset / normalize */
    _body.css          /* estilos del documento */
    _typography.css    /* estilos de text elements: p, h1-h6, a, ul... */
  
  layout/
    _container.css     /* contenedor principal */
    _grid.css          /* sistema de grid */
    _header.css
    _footer.css
    _sidebar.css
  
  components/
    _button.css
    _card.css
    _modal.css
    _form.css
    _badge.css
  
  utilities/
    _display.css       /* .hidden, .block, .flex... */
    _spacing.css       /* .mt-4, .px-2... */
    _text.css          /* .text-center, .text-muted... */
  
  global.css           /* solo @import de todo lo anterior */

El archivo global.css es el único que importas en el layout. Solo contiene @imports en el orden correcto:

/* global.css */
@import './tokens/_colors.css';
@import './tokens/_typography.css';
@import './tokens/_spacing.css';

@import './base/_reset.css';
@import './base/_body.css';
@import './base/_typography.css';

@import './layout/_container.css';
@import './layout/_header.css';
@import './layout/_footer.css';

@import './components/_button.css';
@import './components/_card.css';
@import './components/_modal.css';

@import './utilities/_display.css';
@import './utilities/_spacing.css';

Utilidades vs componentes

Las clases de utilidad hacen una sola cosa y la hacen siempre igual. Son predecibles porque su nombre describe exactamente su efecto:

.hidden       { display: none; }
.sr-only      { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
.text-center  { text-align: center; }
.text-muted   { color: var(--color-text-muted); }
.mt-auto      { margin-top: auto; }
.flex         { display: flex; }
.items-center { align-items: center; }

Los componentes encapsulan la lógica visual completa de un patrón repetible:

/* Componente: no sabes qué hace solo por el nombre, pero encapsula complejidad */
.card { ... }
.btn { ... }
.badge { ... }

La regla práctica: para variaciones pequeñas usa utilidades, para patrones completos usa componentes.


Evitar !important

!important resuelve el síntoma pero no el problema. Cuando te encuentres queriendo usarlo, la pregunta correcta es: ¿por qué la especificidad está tan alta aquí?

Las únicas dos situaciones legítimas para !important:

  1. Overrides de librerías externas donde no controlas el CSS original y no puedes modificarlo
  2. Clases de estado de alta prioridad que siempre deben ganar: .is-hidden { display: none !important; } para ocultar algo independientemente del contexto

El CSS que no tocas no se rompe

La arquitectura CSS no es solo cómo escribes el CSS — también es cómo decides cuándo no escribirlo. Antes de añadir una regla nueva, pregúntate:

  • ¿Ya existe una clase de utilidad para esto?
  • ¿Debería ser una variante del componente existente?
  • ¿Estoy sobreescribiendo algo que debería haber sido diferente desde el principio?

El código CSS que no existe no tiene bugs, no tiene conflictos de especificidad y no hay que mantenerlo.


Con esto termina el Curso de CSS Intermedio. Tienes las herramientas para escribir CSS que escala, que se comporta de forma predecible y que cualquier persona del equipo puede entender y mantener.

El siguiente curso, Curso de JavaScript, añade la capa de comportamiento: variables, condicionales, funciones, arrays y objetos. Es el siguiente paso natural cuando ya entiendes estructura y estilo y quieres hacer que la interfaz responda.

Y si quieres una guía rápida de consulta mientras construyes, tienes la Cheat Sheet completa de CSS.