Arquitectura con capas: el reset, los terceros y las utilidades
El orden canónico de capas de un proyecto, la decisión real que hay detrás de las utilidades, qué queda del reset clásico y cómo se verifica que el sistema sigue sano.
Con la mecánica de @layer entendida, queda la parte que decide si el proyecto será mantenible: elegir las capas y su orden. Es una decisión que se toma una vez, cuesta veinte minutos y determina durante años lo fácil que resulta cambiar cualquier cosa. Esta lección cierra el nivel con el orden que funciona en la práctica, con la justificación de cada posición y con las dos decisiones donde de verdad hay que elegir.
- Justificar la posición de cada capa en un orden canónico de proyecto.
- Elegir entre utilidades normales al final o utilidades importantes al principio.
- Adaptar un reset moderno a un sistema con capas.
- Verificar que el sistema de capas sigue siendo el que se diseñó.
El orden canónico
Un orden que sirve para la inmensa mayoría de los proyectos, de menor a mayor autoridad:
@layer reset, terceros, base, layout, componentes, utilidades;
reset la primera porque es el bloque que debe perder contra absolutamente todo. Su función es fijar un punto de partida, no imponer nada.
terceros inmediatamente después porque es CSS que no controlas y que solo debería ganar al reset. Colocarlo aquí es lo que te libera de conocer sus selectores.
base para los elementos desnudos: tipografía del documento, colores por defecto, tokens, estilos de body, de encabezados, de enlaces, de tablas. Todo lo que apunta a selectores de tipo y a la raíz.
layout para las estructuras de página: rejillas principales, contenedores, envolventes. Va antes que los componentes porque un componente debe poder ajustar su comportamiento dentro de un layout, no al revés.
componentes para las piezas reutilizables, que es donde vivirá el noventa por ciento de tu CSS.
utilidades al final, si eliges la variante normal, que se discute a continuación.
Ese orden se lee de arriba abajo como una frase sobre autoridad, y ese es el criterio para saber si el tuyo está bien: si al leerlo en voz alta no puedes justificar por qué cada una vence a la anterior, hay una capa de más o mal colocada.
El dilema de las utilidades
Aquí hay una decisión real, con dos opciones válidas y consecuencias distintas. Una utilidad es una clase pequeña cuya razón de ser es forzar un valor concreto: un margen, una alineación, ocultar algo. Para que cumpla su función tiene que ganar, y hay dos maneras de conseguirlo.
Opción A: utilidades normales, en la última capa.
@layer reset, terceros, base, layout, componentes, utilidades;
@layer utilidades {
.oculto { display: none; }
.sin-margen { margin: 0; }
}
Ganan a todas las declaraciones normales de todas las capas anteriores, que es lo que necesitas. No ganan a las declaraciones importantes de nadie, cosa que en un proyecto sano no debería ocurrir nunca. Es la opción más limpia y la que recomendaría por defecto.
Opción B: utilidades importantes, en la primera capa.
@layer utilidades, reset, terceros, base, layout, componentes;
@layer utilidades {
.oculto { display: none !important; }
.sin-margen { margin: 0 !important; }
}
Recuerda la inversión: para declaraciones importantes gana la capa más temprana, así que estas utilidades vencen incluso a las declaraciones importantes de las demás capas y a cualquier !important que traiga una hoja de terceros. Es la opción correcta cuando conviven con CSS ajeno agresivo, y es la que usan los sistemas de utilidades más extendidos.
Lo que no funciona es mezclar: utilidades importantes en la última capa son las más débiles de todos los importantes, porque la capa implícita y las capas tardías pierden en la mitad invertida. Si eliges la opción B, la capa tiene que ir la primera; si eliges la A, la última. Y elige una sola: dos capas de utilidades en los dos extremos es un sistema que nadie podrá razonar.
Qué queda del reset
Con capas, un reset no necesita ser débil: ya pierde por estar donde está. Eso simplifica su escritura y elimina dos costumbres del pasado.
Ya no hace falta envolver el reset en :where() para bajarle la especificidad, porque su autoridad no depende de la especificidad. Envolverlo sigue teniendo un uso concreto, pero solo uno: si publicas ese reset para que lo consuman proyectos que quizá no usen capas, :where() es lo que garantiza que cualquiera pueda sobrescribirlo. Para consumo propio dentro de un sistema con capas, es ruido.
Y ya no hace falta un reset grande. Lo que queda es un puñado de decisiones de proyecto:
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin-block: 0; }
img, picture, video, canvas, svg {
display: block;
max-inline-size: 100%;
}
input, button, textarea, select { font: inherit; }
p, h1, h2, h3, h4 { overflow-wrap: break-word; }
:target { scroll-margin-block-start: 3rem; }
}
Nada de eso existe para compensar diferencias entre navegadores: todas son decisiones. Poner el modelo de caja en border-box, quitar los márgenes que estorban a un sistema de espaciado, hacer que las imágenes se comporten como bloques que no desbordan, que los controles de formulario hereden la tipografía —que es de las pocas propiedades que los controles no heredan por defecto— y que el texto largo no rompa el layout.
Aquí hay una idea que todavía no está asimilada por el ecosistema y que va a serlo en los próximos años. Cuando una biblioteca de CSS declara sus capas internas, está ofreciendo puntos de inserción con nombre a quien la consume, y eso es una superficie pública tan real como los nombres de sus clases. Si tu sistema declara @layer sistema.reset, sistema.base, sistema.componentes;, un consumidor puede escribir sus propias reglas en sistema.base para que queden por delante de tu reset pero por detrás de tus componentes, sin tocar un solo selector tuyo y sin !important. Eso es composición de verdad, y es imposible de conseguir con cualquier otra herramienta de CSS. Ahora la contrapartida, que es donde está la lección: cambiar el orden de tus capas internas en una versión menor rompe a tus consumidores exactamente igual que renombrar una clase, y de forma mucho más difícil de diagnosticar, porque nada falla ni desaparece: simplemente empiezan a ganar reglas distintas en sitios que nadie tocó. Por eso, si publicas CSS, hay tres compromisos que conviene adquirir desde la primera versión. Documenta el orden de capas en el mismo sitio donde documentas las clases. Trátalo con las mismas reglas de versionado que cualquier otra ruptura de compatibilidad. Y envuelve todo tu paquete en una capa raíz con el nombre de la biblioteca, para que quien la importe pueda reubicar el conjunto entero en su propia jerarquía con una sola línea. Una biblioteca que emite reglas fuera de toda capa es hoy una biblioteca que se comporta como el CSS más autoritario del proyecto de otro, y eso ya no es aceptable.
Verificar que el sistema sigue en pie
Un sistema de capas se degrada de tres maneras concretas, y las tres se detectan con una comprobación automatizable.
Reglas fuera de toda capa. Es la degradación más común: alguien añade un fichero y olvida envolverlo. Como el CSS sin capa es el más autoritario, ese fichero pasa a ganar a todo el sistema. Detectarlo es contar reglas de estilo que no tengan un ancestro de tipo capa:
const aplanar = (reglas, dentro = false) =>
[...reglas].flatMap((r) =>
r.cssRules
? aplanar(r.cssRules, dentro || r.name !== undefined)
: r.selectorText
? [{ selector: r.selectorText, enCapa: dentro }]
: []
);
const todas = [...document.styleSheets].flatMap((h) => {
try { return aplanar(h.cssRules); } catch { return []; }
});
console.log('sin capa:', todas.filter((r) => !r.enCapa).length, 'de', todas.length);
Capas que aparecen antes de la sentencia de orden. Si la lista real de capas no coincide con la que declaraste, hay un fichero declarando por su cuenta, casi siempre dentro de una regla condicional o dentro de una dependencia.
Declaraciones importantes fuera de la capa de utilidades. Cada una es un síntoma de que faltó una capa. La pregunta ante cualquier !important nuevo es siempre la misma: qué capa hubiera resuelto esto.
Con esas tres comprobaciones en el proceso de integración continua, un sistema de capas se mantiene solo. Sin ellas se erosiona en unos meses, y lo hace en silencio, que es la peor forma de erosionarse.
- Escribe la sentencia de orden que describiría tu proyecto actual y justifica en una frase por qué cada capa vence a la anterior.
- Mete todo el CSS existente en la primera capa y comprueba con el script anterior que no queda nada fuera de capa.
- Escribe una utilidad y decide, con el criterio de esta lección, si va normal al final o importante al principio.
- Importa una dependencia a su propia capa y comprueba que una regla tuya de una sola clase le gana.