Skip to content
This page has been auto-translated and may contain errors.View in English

Organizando y escalando CSS

docs.scrimba.com

La primera hoja de estilos en un nuevo proyecto es un placer escribir. Después de unos pocos cientos de líneas, algo cambia: cambias el color de un botón y otros tres botones cambian también, un encabezado se niega a moverse hasta que pegues !important junto a él, y cada nueva regla parece que podría romper una antigua. El CSS en sí sigue siendo correcto. Lo que se ha agotado es la estructura que lo sostiene, y esa estructura, no la sintaxis, es lo que decide si una hoja de estilos se mantiene manejable a mil líneas o a cien mil.

Por qué CSS se vuelve difícil de escalar

CSS es global por defecto. Cada regla que escribes puede alcanzar cualquier elemento coincidente en cualquier lugar de la página. Escribe p { color: navy; } y cada párrafo en todo el proyecto se vuelve azul marino, aunque no tenías intención. En una página pequeña eso es conveniente. A medida que el proyecto crece, es la raíz de la mayoría de los problemas.

El problema aparece como reglas que colisionan. Dos hojas de estilos apuntan a p, o una regla general y una específica se aplican al mismo elemento, y ahora tienes que averiguar cuál gana. Viste la maquinaria que decide esto en cómo funciona CSS: el navegador resuelve conflictos por especificidad y orden. Escalar CSS bien es principalmente sobre no crear esos conflictos en primer lugar.

CSS no tiene ámbito integrado. Una regla es global: se aplica a cada elemento que coincide con su selector, en cada archivo que carga la página. Nada impide que los estilos de un componente lleguen a otro, porque el lenguaje no tiene noción de "esta regla pertenece a ese componente". Esa libertad es lo que hace que CSS sea rápido para empezar y incómodo para crecer.

A medida que una base de código crece, dos fuerzas te presionan. Las reglas colisionan, porque más selectores significan más superposición, y el navegador resuelve cada choque con las reglas de cómo funciona CSS: especificidad primero, luego orden de origen. Y la especificidad tiende a subir, porque la forma más rápida de ganar un choque hoy es escribir un selector un poco más específico, lo que eleva el piso para todo lo que tiene que sobrescribirlo mañana. Si se deja sin control, terminas con selectores que nadie puede vencer sin !important, que es el punto donde una hoja de estilos deja de sentirse mantenible.

CSS envía un espacio de nombres global plano. Cada selector compite en el mismo espacio, y la cascada, el algoritmo que elige una declaración ganadora cuando varias se aplican a un elemento, resuelve cada conflicto por origen, luego especificidad, luego orden de origen. No hay límite de módulo en el lenguaje en sí, así que un selector escrito para un componente es libre de coincidir con elementos en cualquier otro. Cada técnica de escalado en este capítulo existe para imponer un límite que el lenguaje no te da.

El modo de fallo vale la pena nombrar con precisión, porque los hábitos a continuación son todas defensas contra él. Bajo presión, la corrección más rápida para una regla que no se aplicará es hacer su selector más específico: agregar un padre, encadenar otra clase, recurrir a un id. Cada uno de esos gana el choque inmediato y eleva el piso de especificidad para todo lo que viene después, así que la siguiente sobrescriptura tiene que ser más específica todavía. Esto es una guerra de especificidad: un trinquete de una sola dirección donde los selectores solo suben, terminando en !important porque nada más queda para vencerlos. La salida no es ganar estas batallas de manera más inteligente, sino mantener la especificidad lo suficientemente baja y plana para que raramente comiencen, y, donde el lenguaje ahora lo permite, sacar el ordenamiento de la especificidad completamente con capas en cascada, cubierto al final de este capítulo.

css
/* Una regla global alcanzando cada párrafo en la página */
p {
  color: navy;
}

/* Una segunda regla, en otro lugar, compitiendo por los mismos elementos */
.notice p {
  color: crimson;   /* gana dentro de .notice: más específica */
}
JunoPor qué CSS se vuelve difícil de escalar Las reglas CSS son globales: una regla puede estilizar elementos en toda la página, lo cual es útil al principio y desordenado después. A medida que un proyecto crece, las reglas comienzan a colisionar, y el navegador tiene que elegir un ganador usando especificidad y orden. La mayoría de organizar CSS es sobre escribir reglas que no se peleen entre sí en primer lugar.
JunoPor qué CSS se vuelve difícil de escalar No hay ámbito en CSS, así que cada regla es global y libre de alcanzar cualquier elemento coincidente. A medida que la base de código crece, dos cosas muerden: las reglas colisionan, y la especificidad sube porque la solución rápida para un choque siempre es un selector más específico. Mantén esa suba bajo control y el resto del escalado se vuelve mucho más fácil.
JunoPor qué CSS se vuelve difícil de escalar CSS es un espacio de nombres global plano sin límite de módulo, así que la cascada resuelve cada choque por origen, luego especificidad, luego orden. La trampa es el trinquete de especificidad: cada sobrescriptura rápida eleva el piso, así que la siguiente tiene que subir más alto, y terminas en !important. Cada técnica en este capítulo es una forma de mantener la especificidad lo suficientemente baja para que el trinquete nunca comience a girar.

Mantén la especificidad baja y plana

El hábito más útil es estilizar con clases, principalmente una clase a la vez. Una clase como .card es rápida de aplicar, reutilizable, e indolora de sobrescribir más tarde si lo necesitas, porque una sola clase es un nivel bajo y suave de especificidad.

El problema viene de dos cosas: ids y largas cadenas de selectores. Un id como #header es mucho más difícil de sobrescribir que una clase, y una cadena como .sidebar ul li a está vinculada a una estructura exacta. Prefiere una clase sencilla que puedas poner donde quieras:

El hábito principal para mantener CSS mantenible es mantener la especificidad baja y plana: estiliza con clases simples, y evita las dos cosas que disparan la especificidad, ids y cadenas de descendientes profundas. Una sola clase es el punto dulce. Es lo suficientemente específica para apuntar a lo que significas y lo suficientemente débil para que otra clase simple pueda sobrescribirla más tarde sin una batalla.

Los ids puntúan mucho más alto que las clases, así que una regla #id es dolorosa de sobrescribir y te fuerza hacia arriba. Las cadenas de descendientes largas causan un problema diferente: .sidebar nav ul li a eleva la especificidad y suelda la regla a una HTML estructura exacta, así que se rompe en el momento en que el marcado cambia. Dale al elemento su propia clase y apunta a eso directamente.

Mantener la especificidad baja y plana es el hábito que previene guerras de especificidad en lugar de ganarlas. Baja significa que cada regla puntúa tan poco como puede mientras sigue seleccionando correctamente; plana significa que las reglas se agrupan alrededor del mismo peso bajo, así que cualquiera de ellas puede sobrescribir cualquier otra por orden de origen únicamente. Una hoja de estilos donde casi todo es un selector de clase simple tiene esta propiedad: nada es difícil de vencer, porque nada está puntuado por encima de sus vecinos.

Dos construcciones rompen la planitud y ambas valen la pena evitar por defecto. Los ids contribuyen un orden de magnitud más especificidad que una clase, así que una regla #id simple se sienta por encima de cualquier pila de reglas de clase y solo puede ser vencida por otro id o por !important; estiliza con clases y reserva ids para enlaces de fragmento y ganchos de JavaScript. Las cadenas de descendientes profundas elevan la especificidad por selector y acoplan la regla a una ascendencia fija, así que .sidebar nav ul li a es tanto difícil de sobrescribir como frágil: cambia el marcado y silenciosamente deja de coincidir. La metodología en la siguiente sección existe en gran medida para dejarte nombrar un elemento directamente, así que nunca llegas a una cadena para encontrarlo.

css
/* Preferir: una sola clase, baja y plana */
.nav-link {
  color: navy;
}

/* Evitar: un id, difícil de sobrescribir después */
#nav-link {
  color: navy;
}

/* Evitar: una cadena profunda, frágil y especificidad más alta */
.sidebar nav ul li a {
  color: navy;
}
JunoMantén la especificidad baja y plana Estiliza con clases, principalmente una clase a la vez, como .card o .nav-link. Una sola clase es rápida de reutilizar e indolora de sobrescribir más tarde, que es exactamente lo que quieres. Aléjate de ids como #header y cadenas largas como .sidebar ul li a, porque ambas son mucho más difíciles de cambiar.
JunoMantén la especificidad baja y plana Las clases simples son el punto dulce: específicas lo suficiente para golpear lo que significas, débiles lo suficiente para que otra clase pueda sobrescribirlas sin una batalla. Los ids disparan la especificidad y te arrastran hacia arriba, y las cadenas profundas como .sidebar nav ul li a se rompen en el momento en que el marcado cambia. Cuando una regla es difícil de colocar, dale al elemento su propia clase y apunta a esa.
JunoMantén la especificidad baja y plana Bajo y plano significa que cada regla puntúa cerca del mismo peso pequeño, así que el orden de origen únicamente puede resolver cualquier choque. Los ids se sientan un orden de magnitud por encima de las clases y solo !important u otro id los vence, así que guárdalos para enlaces de fragmento y ganchos de JS. Las cadenas profundas tanto elevan la especificidad como sueldan una regla a una forma HTML, que es por qué nombrar el elemento directamente vence buscarlo a través de sus ancestros.

Una convención de nomenclatura

Una vez que estás estilizando con clases, la siguiente pregunta es cómo llamarlas. Nombres como .blue o .thing2 se caen rápido, porque no dicen nada sobre para qué es la clase. Una convención de nomenclatura es una forma acordada de nombrar clases para que el nombre te diga qué hace una clase y dónde pertenece.

La ampliamente utilizada se llama BEM, que significa Block, Element, Modifier. Un bloque es un componente como una tarjeta. Un elemento es una parte dentro de él, escrito con dos guiones bajos. Un modificador es una variación, escrito con dos guiones:

Con las clases como la unidad de estilización, la nomenclatura se convierte en la cosa que las mantiene organizadas. Una convención de nomenclatura es un patrón compartido para nombres de clase, y su trabajo es hacer nombres predecibles y libres de colisiones: puedes decir por un nombre de clase qué componente pertenece y qué parte estiliza, y dos componentes nunca accidentalmente reutilizan el mismo nombre.

La convención más ampliamente adoptada es BEM (Block, Element, Modifier). El bloque es el componente (.card), un elemento es una parte de él unida con dos guiones bajos (.card__title), y un modificador es una variante unida con dos guiones (.card--featured). El beneficio es especificidad plana: porque cada parte obtiene su propia clase simple, nunca necesitas una cadena de descendientes para alcanzar .card__title, así que BEM y el hábito bajo-y-plano se refuerzan mutuamente.

Una convención de nomenclatura sustituye el ámbito que el lenguaje nunca tuvo. Al codificar un límite de componente en el nombre de clase en sí, te da nombres libres de colisiones y autodocumentados sin ninguna característica del lenguaje: lee una clase y sabes su componente, su parte y su variante. Cualquier convención consistente entrega esto; el valor está en la consistencia, no en la puntuación exacta.

BEM (Block, Element, Modifier) es la más ampliamente utilizada. El bloque nombra el componente (.card), un elemento nombra una parte con un doble guion bajo (.card__title), y un modificador nombra una variante con un doble guion (.card--featured). Lo que hace que BEM cumpla su peso más allá de la nomenclatura es que mantiene la especificidad plana por construcción: cada elemento obtiene su propio selector de clase simple, así que diriges .card__title directamente en lugar de escribir .card .title, y cada regla en el componente se sienta en el mismo peso. Esa es la misma propiedad baja-y-plana de la sección anterior, ahora saliendo del esquema de nomenclatura gratis. El costo es listas de clases verbosas en el HTML, que es un intercambio que la mayoría de los equipos aceptan por la previsibilidad que compra.

css
/* Bloque: el componente en sí */
.card { }

/* Elemento: una parte del bloque, dos guiones bajos */
.card__title { }
.card__body { }

/* Modificador: una variante del bloque, dos guiones */
.card--featured { }
html
<article class="card card--featured">
  <h2 class="card__title">Taller de fin de semana</h2>
  <p class="card__body">Una breve introducción al diseño de página.</p>
</article>
JunoUna convención de nomenclatura Una convención de nomenclatura es una forma acordada de nombrar clases para que el nombre te diga para qué es. BEM es la común: un bloque como .card, un elemento dentro de él como .card__title con dos guiones bajos, y una variante como .card--featured con dos guiones. No tienes que usar BEM, pero elige un esquema consistente y cúmplelo.
JunoUna convención de nomenclatura Una convención hace nombres de clase predecibles y libres de colisiones, así que un nombre te dice su componente y su parte. BEM es la ampliamente utilizada: bloque .card, elemento .card__title, modificador .card--featured. También mantiene la especificidad plana, porque cada parte obtiene su propia clase simple en lugar de una cadena de descendientes.
JunoUna convención de nomenclatura Una convención se mantiene en lugar del ámbito que CSS nunca te dio: el límite del componente vive en el nombre de clase, así que los nombres permanecen libres de colisiones y autodocumentados. BEM codifica bloque, elemento y modificador como .card, .card__title, .card--featured, y su verdadero beneficio es especificidad plana por construcción, ya que cada parte es una sola clase. El precio es listas de clases verbosas en el marcado, que generalmente vale la pena por la previsibilidad.

Estructurando los archivos

A medida que una hoja de estilos crece, un archivo largo se vuelve difícil de mover. La solución común es dividir CSS en algunas carpetas por lo que hacen las reglas: estilos base (los valores predeterminados para elementos simples como body y encabezados), componentes (las tarjetas, botones y otras piezas), y utilidades (minúsculos ayudantes de un propósito único como una clase de espaciado o alineación de texto).

Un detalle importa cuando divides archivos: el orden en que los cargas. Cuando dos reglas tienen la misma especificidad, la que viene después gana, así que una hoja de estilos cargada después puede sobrescribir una anterior. Carga tus archivos de más general a más específico:

Dividir CSS en carpetas por función mantiene una base de código creciente navegable. Una estructura común es tres grupos: base (resets y valores predeterminados de elementos como body, encabezados y enlaces), componentes (piezas autocontenidas como .card y .btn), y utilidades (ayudantes de un propósito único como .text-center o .mt-4).

El orden en que concatenas o importas estos archivos no es cosmético, porque el orden de origen es un desempate en cascada: cuando dos reglas tienen especificidad igual, la posterior gana. Así que cargas de menos a más específico, base primero, luego componentes, luego utilidades última, para que una utilidad pueda sobrescribir un componente y un componente pueda sobrescribir un valor predeterminado base sin que nadie eleve la especificidad para forzarlo. Obtener el orden correcto es lo que te permite mantener todo en especificidad de clase simple y aún así tener sobrescrituras aterrizadas donde esperas.

Estructurar archivos por función es cómo mantienes CSS bajo-y-plano navegable a escala, y el orden de carga es fundamental. Una división convencional es base (resets y valores predeterminados de elemento desnudo), componentes (piezas encapsuladas, una por archivo), y utilidades (ayudantes atómicos de propiedad única). La regla de ordenamiento sigue directamente de la cascada: con especificidad mantenida deliberadamente plana, el orden de origen se convierte en el desempate primario, así que la secuencia en que se cargan los archivos decide qué regla de peso igual gana.

Por eso el orden corre de menos a más específico en intención, base, luego componentes, luego utilidades, para que grupos posteriores puedan sobrescribir los anteriores sin ningún aumento de especificidad. Una utilidad .text-center debe vencer la alineación de texto del propio componente, y puede, puramente porque carga última en el mismo peso. Esto funciona, pero es una convención reforzada por disciplina: nada en el lenguaje detiene a alguien de importar utilidades antes de componentes e invertir silenciosamente el esquema completo. Confiar en el orden de origen en un equipo grande es frágil exactamente por esa razón, que es lo que motiva hacer el ordenamiento explícito en lugar de posicional, el tema de la última sección.

css
/* main.css: el orden corre de menos a más específico */
@import "base/reset.css";        /* valores predeterminados de elemento */
@import "base/typography.css";

@import "components/card.css";    /* piezas autocontenidas */
@import "components/button.css";

@import "utilities/spacing.css";  /* cargadas última para que puedan sobrescribir */
@import "utilities/text.css";
JunoEstructurando los archivos Divide CSS en carpetas por lo que hacen las reglas: base para valores predeterminados de elementos, componentes para las piezas como tarjetas y botones, y utilidades para minúsculos ayudantes. Luego mira el orden en que los cargas, porque cuando la especificidad empatada, la regla posterior gana. Carga general primero y específico último, así los ayudantes pueden sobrescribir las piezas.
JunoEstructurando los archivos Agrupa archivos por función: base, componentes, luego utilidades. Cárgalos de menos a más específico, porque el orden de origen es el desempate cuando la especificidad es igual, así que una utilidad cargada última puede sobrescribir un componente sin ningún aumento de especificidad. Obtener el orden correcto es lo que te permite mantener todo en peso de clase simple y aún así tener sobrescrituras aterrizadas.
JunoEstructurando los archivos Una vez que la especificidad está deliberadamente plana, el orden de origen se convierte en el desempate primario, así que el orden de carga de archivo decide qué regla de peso igual gana. Base, luego componentes, luego utilidades significa que grupos posteriores sobrescribirán los anteriores gratis, sin aumento de especificidad necesario. La trampa es que es convención mantenida por disciplina: importar utilidades demasiado pronto e inviertes el esquema completo, que es exactamente por qué el siguiente paso hace el ordenamiento explícito.

Haciendo la cascada explícita

Todo hasta ahora mantiene la cascada manejable por convención: especificidad baja, buenos nombres, orden de archivo cuidadoso. Hay más aquí a medida que profundizas, y vale la pena conocer la dirección del viaje. CSS moderno te permite tomar control del ordenamiento directamente en lugar de confiar en dónde sucede estar un archivo, y te da formas de mantener la cascada predecible a medida que más personas trabajan en la misma hoja de estilos. Encontrarás estas herramientas a medida que tus proyectos crecen, y los hábitos anteriores son lo que te prepara para ellos.

Las técnicas hasta ahora manejan la cascada indirectamente, a través de especificidad y orden de origen. CSS más nuevo te permite manejarla directamente. Una capa en cascada, escrita con la regla @layer, es un cubo de ordenamiento nombrado: declara las capas por adelantado en el orden en que quieres que ganen, y cada regla dentro de una capa vence cada regla en una capa anterior, sin importar la especificidad.

css
/* Declara el orden ganador una vez, de más perdedor a más ganador */
@layer base, components, utilities;

@layer components {
  .card { padding: 16px; }
}

@layer utilities {
  .p-0 { padding: 0; }   /* gana sobre .card a pesar de especificidad igual */
}

Porque una capa posterior siempre gana, ya no necesitas que el orden de archivo sea perfecto, y raramente necesitas !important para forzar una sobrescriptura. Ese es el beneficio práctico: las capas sacan el ordenamiento del territorio frágil de "qué archivo cargó primero" y lo ponen en una declaración explícita que todos pueden leer.

El orden de origen es frágil porque es posicional: funciona hasta que alguien reordena una importación. Las capas en cascada, la regla @layer, reemplazan eso con ordenamiento explícito que se sienta encima de la especificidad en la cascada. Declaras el orden de capa una vez, y la cascada consulta el orden de capa antes de mirar siquiera la especificidad, así que una regla en una capa posterior vence una regla en una anterior incluso cuando la regla anterior es más específica. El ordenamiento se convierte en una decisión nombrada, legible en lugar de un efecto secundario de posición de archivo.

css
/* Una línea arregla el orden ganador para toda la base de código */
@layer reset, base, components, utilities;

@layer components {
  .card__title { font-size: 1.25rem; }
}

@layer utilities {
  .text-lg { font-size: 1.5rem; }   /* gana: utilities es la capa posterior */
}

Esto remoldea dos hábitos. Primero, desactiva la guerra de especificidad: porque el orden de capa supera la especificidad, dejas de alcanzar selectores más específicos para ganar un choque, y casi nunca necesitas !important, cuyo trabajo completo era escapar de una especificidad que no podrías vencer de otra manera. (Las capas incluso doman !important en sí, invirtiendo su precedencia para que una declaración importante en una capa anterior gane, pero el objetivo es necesitarlo tan raramente que esto permanece como trivia.) Segundo, aclara la elección de largo tiempo entre dos estilos de organización: un enfoque utility-first, componiendo páginas de muchas clases atómicas de propiedad única, y un enfoque de componente, agrupando estilos detrás de una clase semántica por pieza. La mayoría de bases de código de producción ejecutan ambas, y las capas les permiten coexistir por diseño: pon componentes en una capa components y utilidades en una capa utilities posterior, y una utilidad confiablemente sobrescribe un componente sin que ninguno de los lados escale la especificidad. La decisión deja de ser "cuál gana la batalla en cascada" y se convierte en "cuál se lee mejor para esta pieza de IU", porque la cascada es resuelta por el orden de capa, no por quién escribió el selector más agresivo. Esa previsibilidad es el verdadero premio a medida que un equipo crece: el orden ganador es una declaración que todos pueden leer, no conocimiento tribal sobre secuencia de importación. Cuando necesitas compartir valores como colores y espaciado entre estas capas, propiedades personalizadas te permiten definirlas una vez en lugar de duplicarlas.

JunoHaciendo la cascada explícita Todo hasta ahora doma la cascada con hábitos: especificidad baja, nombres claros, orden de carga cuidadoso. A medida que avanzas, CSS te da herramientas para establecer el orden ganador directamente en lugar de apoyarse en qué archivo cargó primero. No las necesitas todavía, y los hábitos en este capítulo son exactamente lo que te prepara para ellos.
JunoHaciendo la cascada explícita Una capa en cascada con @layer es un cubo de ordenamiento nombrado: declara el orden de capa por adelantado y una capa posterior vence una anterior sin importar la especificidad. Eso significa que el orden de archivo deja de tener que ser perfecto y raramente necesitas !important para forzar una victoria. Saca el ordenamiento del territorio frágil de posición de archivo y lo pone en una línea que todos pueden leer.
JunoHaciendo la cascada explícita Las capas en cascada se sientan encima de la especificidad, así que una capa posterior gana incluso contra una regla más específica, que libera el trinquete de especificidad y jubila la mayoría de tus usos de !important. También permiten que estilos utility-first y de componentes coexistan: componentes en una capa, utilidades en una posterior, y una utilidad sobrescribe un componente sin que ninguno escale. La victoria a medida que un equipo crece es que el ordenamiento es una declaración legible, no conocimiento tribal sobre secuencia de importación.