wandres.dev
GRID II · Áreas, líneas nombradas, auto-placement

grid-auto-flow: dense y el orden de lectura

Cómo el empaquetado denso rellena los huecos reordenando visualmente los elementos, qué se rompe al hacerlo, cómo medir el daño y en qué casos concretos es aceptable.

⏱ 16 min

grid-auto-flow: row dense rellena los huecos que deja el empaquetado por defecto, y el resultado visual suele ser notablemente mejor. El precio es que el orden visual deja de coincidir con el orden del documento, y ese desajuste no lo percibe quien mira la pantalla pero sí quien navega con teclado o con lector de pantalla. Es una de las pocas propiedades de CSS con un coste de accesibilidad directo y medible, y merece que la decisión se tome a conciencia.

🎯 Al terminar esta lección sabrás
  • Explicar qué cambia dense en el algoritmo de colocación.
  • Describir el desajuste que produce entre orden visual y orden de foco.
  • Reproducir el problema con el tabulador y comprobarlo.
  • Enunciar los casos en los que dense es aceptable.

Qué cambia exactamente

La palabra clave dense se añade al valor de grid-auto-flow, junto a row o column:

.rejilla {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-flow: row dense;
  gap: 0.5rem;
}

El cambio en el algoritmo es de una sola línea: en lugar de que el cursor conserve su posición entre elementos, se reinicia al principio de la rejilla antes de colocar cada uno. El efecto es que cada elemento busca sitio desde el primer hueco disponible, no desde donde se quedó el anterior.

Con el ejemplo de la lección anterior —tres columnas, cuatro elementos, el tercero de dos columnas de ancho— el resultado cambia:

Disperso (por defecto):     Denso:
[ A ][ B ][   ]             [ A ][ B ][ D ]
[   C    ][ D ]             [   C    ][   ]

D, que necesitaba una sola columna, encuentra sitio en la celda 3 de la primera fila. El hueco desaparece.

📝
Denso no significa compacto óptimo

dense no resuelve el problema de empaquetado óptimo, que es computacionalmente caro. Es un algoritmo voraz: para cada elemento, en orden, coge el primer hueco donde quepa. Puede seguir dejando huecos si el orden de los elementos no permite rellenarlos, y una permutación distinta de los mismos elementos puede dar un resultado más compacto.

Lo que se rompe

El orden en el que un usuario recorre los elementos interactivos con el tabulador lo determina el orden del DOM. El orden en el que un lector de pantalla anuncia el contenido lo determina el orden del DOM. dense no toca el DOM: solo cambia dónde se dibuja cada caja.

El resultado con el ejemplo anterior: al tabular, el foco va de A a B, luego salta abajo a la izquierda a C, y después vuelve arriba a la derecha a D. Para quien ve la pantalla, el foco se mueve dando un salto hacia atrás y hacia arriba que no tiene ninguna lógica visible.

En una galería de treinta elementos con tamaños variables, el foco puede saltar por toda la rejilla de forma aparentemente aleatoria. Un usuario con motricidad reducida que navega con teclado pierde por completo el hilo, y uno con visión parcial que usa magnificador puede quedarse sin saber dónde está el foco.

Esto no es una hipótesis. Es el mismo problema que las WCAG describen en el criterio 1.3.2, Secuencia significativa, y en el 2.4.3, Orden del foco. Ambos son de nivel A, el más básico.

⚠️
La prueba de los treinta segundos

Abre tu rejilla con dense, haz clic en el primer elemento interactivo y pulsa el tabulador diez veces mirando dónde aparece el anillo de foco. Si en algún momento salta hacia atrás o hacia arriba de forma que no puedes anticipar, tienes el problema. Es la única prueba que hace falta y tarda medio minuto.

Cuándo es aceptable

Hay un criterio que funciona y se puede aplicar sin discutir: dense es aceptable cuando el orden de los elementos no comunica nada y ninguno de ellos es interactivo por separado.

Es aceptable en una galería de fotos decorativas sin enlaces individuales, en un mosaico de logotipos, en un fondo de piezas visuales. Ahí no hay orden que romper porque no había orden que respetar.

Es dudoso en una galería de fotos donde cada una es un enlace. El desajuste del foco es real, aunque el contenido no tenga jerarquía.

No es aceptable en resultados de búsqueda, en listas ordenadas por relevancia o fecha, en un panel de productos con precios, ni en nada donde el usuario tenga motivos para asumir que el orden significa algo. Y menos aún si hay elementos interactivos: ahí dense está creando un problema de navegación real a cambio de eliminar un hueco.

/* Aceptable: mosaico decorativo sin interacción */
.mosaico {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
  grid-auto-rows: 8rem;
  grid-auto-flow: row dense;
  gap: 0.25rem;
}
.mosaico img { inline-size: 100%; block-size: 100%; object-fit: cover; }

Las alternativas al hueco

Si tienes huecos y no puedes usar dense, hay tres salidas.

Uniformar los tamaños. Los huecos aparecen porque hay elementos de amplitudes distintas. Si todos ocupan una pista, no hay huecos posibles.

Ordenar los datos en el servidor. Si controlas el orden en el que llegan los elementos, puedes emitirlos en un orden que no deje huecos. El orden del DOM y el visual siguen coincidiendo porque el DOM es el que has cambiado, y eso sí es legítimo.

Aceptar el hueco. Suele ser la respuesta correcta. Una celda vacía en una rejilla es una imperfección visual menor que casi ningún usuario nota, y desde luego menos grave que un orden de foco impredecible.

Y un apunte sobre masonry, el layout tipo mampostería que rellena verticalmente sin huecos: sigue siendo una discusión abierta en el grupo de trabajo, con propuestas sintácticas distintas sobre la mesa, y no es algo con lo que puedas contar en producción. Cuando alguien pide “un grid tipo Pinterest”, lo que existe hoy es dense con sus limitaciones o una implementación en JavaScript.

dense es el ejemplo perfecto de una decisión que parece de CSS y es de producto

Hay una categoría de decisiones técnicas que no se pueden tomar desde el editor de código, y dense es el caso de manual. La propiedad es trivial: una palabra, un cambio de comportamiento documentado, cero incompatibilidades. Lo que no es trivial es que la elección correcta depende de información que no está en el CSS: si esos elementos son interactivos, si el orden significa algo para el usuario, cuántas personas navegan esa vista con teclado, si es un flujo crítico o una página decorativa. Ninguna de esas cosas se puede deducir del código, y ninguna herramienta automática puede decidirlo por ti: un linter de accesibilidad puede avisarte de que uses dense, pero no puede saber si en tu caso está justificado. Esto define un tipo de responsabilidad que se lleva mal con la forma en que se suele repartir el trabajo en un equipo: quien conoce la propiedad no suele conocer el contexto de uso, y quien conoce el contexto no sabe que la propiedad existe. La consecuencia práctica es que decisiones como esta hay que documentarlas en el propio código, con un comentario que diga por qué se consideró aceptable, porque de lo contrario la siguiente persona que toque ese archivo copiará la línea a un sitio donde no lo es. Es la misma disciplina que se aplica a cualquier eslint-disable o a cualquier unsafe: la excepción se permite, pero se justifica por escrito.