El sidebar que colapsa solo
Una barra lateral de ancho fijo junto a un contenido fluido que se apila cuando deja de haber sitio, sin una sola media query. Por qué este patrón solo es posible con Flexbox.
El layout de barra lateral es la prueba de fuego del diseño intrínseco: una columna que quiere medir siempre lo mismo, otra que quiere ocupar el resto, y un punto de cambio en el que las dos deben apilarse porque juntas ya no caben. Ese punto no lo decide el viewport: lo decide el ancho del contenedor y los tamaños que has declarado. La solución cabe en cinco líneas de Flexbox y depende de un detalle del algoritmo que casi nadie ha leído.
- Construir un sidebar intrínseco con
flex-wrapy una base porcentual en el contenido principal. - Explicar por qué el umbral de apilado sale de la suma de las bases de flex.
- Justificar el uso de un
flex-growdesproporcionado en la columna principal. - Argumentar por qué Grid no puede reproducir este patrón por sí solo.
El patrón
.con-lateral {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.con-lateral > .lateral {
flex-grow: 1;
flex-basis: 18rem; /* su tamano deseado */
}
.con-lateral > .principal {
flex-grow: 999; /* se queda con todo el sobrante */
flex-basis: 0;
min-inline-size: 60%; /* el umbral de apilado */
}
Sin media queries, sin container queries y sin JavaScript: cuando el contenedor es ancho, aparecen dos columnas con la lateral en torno a sus 18rem; cuando se estrecha, las dos se apilan a ancho completo. Funciona igual dentro de una plantilla de página que dentro de un modal de 400px.
La mecánica: umbral y reparto
De dónde sale el umbral
Flexbox decide si un elemento cabe en la línea actual comparando la suma de los tamaños base hipotéticos con el espacio disponible. El tamaño base de la lateral es 18rem. El de la principal está gobernado por min-inline-size: 60%, que actúa como suelo: por pequeña que sea su base declarada, su tamaño mínimo es el 60% del contenedor.
Así que la línea cabe mientras 18rem + 1.5rem + 60% sea menor o igual que el ancho del contenedor. Despejando: el 40% del contenedor tiene que llegar a 19,5rem, es decir, hacen falta unos 48,75rem de contenedor. Por debajo de eso no cabe, flex-wrap: wrap manda la principal a una segunda línea, y como cada línea reparte su espacio de forma independiente, las dos acaban a ancho completo.
El umbral, por tanto, está expresado en las mismas unidades que el diseño: el ancho que quieres para la lateral y la proporción mínima que quieres para el contenido. Si mañana la lateral pasa a 22rem, el punto de apilado se recalcula solo. Con una media query habría que recalcularlo a mano y actualizar el número mágico.
min-inline-size y flex-basis son propiedades lógicas y físicas mezcladas con criterio: flex-basis ya opera sobre el eje principal, sea cual sea la dirección, mientras que min-width es literalmente horizontal. En un documento en modo vertical o con flex-direction: column, min-inline-size sigue significando lo que quieres decir.
Por qué 999 y no 1
flex-grow: 999 en la principal parece arbitrario y no lo es. Cuando las dos columnas comparten línea hay espacio sobrante, y flex-grow reparte ese sobrante en proporción a los valores. Con 1 y 1, el sobrante se parte por la mitad y la lateral crece por encima de sus 18rem, con lo que deja de tener un ancho estable. Con 1 en la lateral y 999 en la principal, la lateral recibe una milésima parte del sobrante —despreciable— y se queda prácticamente en su base.
¿Por qué no flex-grow: 0 en la lateral, que sería lo obvio? Porque entonces, en la línea donde está sola tras el apilado, no crecería: se quedaría en 18rem con el resto en blanco. El 1 es lo que la hace ocupar el ancho completo cuando está sola. El truco del 999 resuelve los dos casos con una sola declaración, y es el motivo por el que este patrón se escribe así en todas partes.
Puedes ajustar la agresividad cambiando el número: con flex-grow: 4 la lateral se llevaría un quinto del sobrante, lo que a veces es deseable si quieres que crezca un poco en pantallas muy anchas.
Por qué Grid no puede hacer esto solo
Es tentador buscar el equivalente en Grid y no existe, por una razón estructural que conviene entender. El apilado de este patrón ocurre porque las líneas de flex se rompen en función de los tamaños del contenido. Grid no tiene un mecanismo equivalente: grid-template-columns declara pistas que existen siempre, y repeat(auto-fit, ...) decide el número de pistas pero las hace todas iguales, con lo que pierdes la asimetría entre lateral y principal.
Lo más cerca que llega Grid sin ayuda externa es esto, y no es lo mismo:
/* dos columnas asimetricas, pero nunca se apilan */
.con-lateral-grid {
display: grid;
grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
gap: 1.5rem;
}
/* columnas iguales que si se apilan, pero pierden la asimetria */
.iguales {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
gap: 1.5rem;
}
La versión de Grid que sí funciona necesita una condición externa que le diga cuándo cambiar de plantilla, y esa condición hoy se escribe con una container query sobre el contenedor. Es una solución perfectamente buena y probablemente la que acabes usando; pero es una solución condicional, mientras que la de Flexbox es incondicional: no hay ninguna regla que se active o se desactive, solo un algoritmo que da resultados distintos con entradas distintas.
Variantes útiles
Con el mismo esqueleto salen tres patrones más sin añadir conceptos:
/* lateral a la derecha: cambia el orden en el DOM, no con order */
.con-lateral-dcha > .lateral { flex-grow: 1; flex-basis: 18rem; }
/* dejar que la lateral crezca un quinto del sobrante en pantallas anchas */
.elastica > .lateral { flex: 1 1 18rem; }
.elastica > .principal { flex: 4 1 0; min-inline-size: 60%; }
/* tres zonas: lateral, principal y panel de apoyo */
.tres { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.tres > .lateral { flex: 1 1 14rem; }
.tres > .principal { flex: 999 1 0; min-inline-size: 50%; }
.tres > .apoyo { flex: 1 1 14rem; }
En la variante de tres zonas fíjate en que el umbral pasa a ser 14rem + 14rem + 2 huecos + 50%: al añadir una tercera columna, el punto de apilado se recalcula solo a partir de los valores nuevos. Y en la elástica, bajar el flex-grow de la principal de 999 a 4 reparte el sobrante en proporción 4 a 1, con lo que la lateral gana algo de ancho en monitores grandes en vez de quedarse clavada.
Este patrón parece un truco y es lo contrario: es el único sitio del CSS donde puedes expresar un umbral condicional sin escribir una condición. La media query, la container query y el @supports son todas la misma forma: una condición evaluada contra un estado, con dos ramas y el consiguiente riesgo de que las ramas se desincronicen. flex-wrap no es eso. Es un algoritmo que produce un cambio cualitativo de layout como efecto secundario de un cálculo cuantitativo, exactamente igual que un texto cambia de número de líneas sin que nadie haya escrito una regla de “si el texto mide más de tanto”. Por eso el umbral no puede quedarse obsoleto: no está guardado en ningún sitio, se deriva cada vez de las cantidades que sí declaraste. Cuando entiendes esto dejas de ver flex-wrap como “que quepa lo que quepa” y empiezas a verlo como el mecanismo más antiguo y más robusto de adaptación de la web —el mismo que hace funcionar un párrafo— aplicado a cajas en vez de a palabras.