¿Qué hace layout?
Esta habilidad decide dónde van las cosas, qué tan separadas están y qué tamaño tienen. Toma las mismas decisiones en el mismo orden y verifica el resultado contra los píxeles renderizados.
Antes de cualquier decisión, aplica product-ui §1 (el diario de gusto y precedencia) y §5 (qué habilidad posee qué).
Decidir en este orden
- Cascarón. Nombra las regiones de la pantalla: barra lateral, barra de herramientas, contenido, inspector. Cada región es propietaria de un desplazamiento o ninguno. Detalles y anchos: shell-and-responsive.md. Por qué: el cascarón fija cada ancho dentro del cual debe vivir el diseño interior.
- Ancho y medida. Establece la columna de contenido según lo que contiene: formulario 440px, lectura 720px con prosa limitada a la medida de
type(66ch), datos 1200px. Una columna nunca se estira porque la pantalla sea ancha. Por qué: más allá de 75 caracteres una línea pierde al lector, y un formulario estirado envía los ojos a través de la pantalla. - Espacio. Usa la escala a continuación y nada más. Agrupa primero, con espacio; añade un relleno, luego una línea, solo donde el espacio no puede soportarlo. spacing-and-density.md.
- Alinear. Elige los bordes compartidos y coloca todo sobre ellos. Un error de 1–4px es un bug. Corrige ópticamente donde la geometría se ve mal. spacing-and-density.md.
- Tamaño. Elige la densidad para la región, luego las áreas de interacción. spacing-and-density.md.
- Datos en el peor caso. Redimensiona la caja para el contenido más largo, más corto, faltante, traducido y aún cargándose antes de darlo por terminado. stability-and-text.md.
- Adaptar. Haz que la estructura responda a su contenedor, no al dispositivo. shell-and-responsive.md.
- Medir. Renderiza a 2×, mide espacios y bordes, corrige lo que muestran los píxeles. measure.md.
La escala
Una escala, base 4px. Nunca escribas un valor de espaciado que no sea un paso. Por qué: los valores únicos (10px, 14px, 18px) hacen que el ritmo sea imposible de ver e imposible de mantener.
| Token | px | Uso típico |
|---|---|---|
--space-xs | 4 | etiqueta a su descripción; icono a etiqueta en un chip |
--space-sm | 8 | elementos dentro de un grupo; padding de control en bloque |
--space-md | 12 | entre controles con borde; padding de control en línea |
--space-lg | 16 | entre campos; padding compacto de tarjeta |
--space-20 | 20 | entre bloques dentro de una tarjeta |
--space-xl | 24 | entre grupos; padding cómodo de tarjeta |
--space-2xl | 32 | entre secciones |
--space-3xl | 48 | encima del primer título de una página |
--space-4xl | 64 | debajo del último bloque de una página |
Ajustes de 1–2px para alineación óptica son los únicos valores fuera de la escala. Nómbralos (--play-nudge: 2px).
Reglas que no necesitan referencia
- Agrupa con espacio. El salto entre grupos es al menos 2× el espacio dentro de uno: 8px dentro significa 16px o más entre grupos. Espaciado igual en todas partes hace que la agrupación sea ambigua, y la ambigüedad aquí es un bug funcional.
- Elimina líneas antes de añadirlas. Espacio primero, un relleno segundo, un divisor al final, y solo para datos densos. Por qué: cada línea cuesta atención que la misma información da gratis con espacio.
- Espacio arriba igual a espacio abajo para cualquier cosa entre dos elementos. Por qué: una cosa más cerca de un vecino se lee como propiedad de este.
- Los controles deben verse como controles. Un botón, enlace o interruptor tiene una forma, un subrayado o una posición en la barra de herramientas. El texto estático nunca comparte esos. Por qué: un control con estilo de texto se oculta, y el texto con estilo de control acumula clics muertos.
- Las acciones se colocan sobre los datos que afectan, nunca debajo de ellos. Un control que establece algo se coloca junto a ello.
- El contenido principal va primero y al inicio. El orden visual es igual al orden del DOM: nunca uses
order,row-reverseni colocación en grid para reordenar contenido. Por qué: el orden del teclado y del lector de pantalla sigue el DOM. - Propiedades lógicas (
margin-inline-start,padding-block,inset-inline-end,text-align: start) para cualquier cosa que se refleje. Por qué: RTL entonces funciona sin una segunda hoja de estilo. - Nunca coloques controles críticos en el borde inferior de una ventana o barra lateral. Por qué: las personas empujan las ventanas parcialmente fuera de la pantalla.
- El desplazamiento muestra lo que está oculto. El contenido más allá de un borde recibe una señal visible: un vistazo de 16–32px o un degradado.
- Nada se desplaza. El hover, la selección y el foco nunca cambian el tamaño de un elemento. Los datos cargados ocupan la caja que tenía el esqueleto.
Pares
| Roto | Arreglado | Por qué |
|---|---|---|
gap: 12px en el formulario y gap: 12px entre sus fieldsets | gap: 8px dentro, gap: 24px entre | Los grupos necesitan un salto de al menos 2× |
padding: 10px 14px | padding: var(--space-sm) var(--space-md) | Solo pasos |
| Título en x=24, descripción en x=26 | Ambos en x=24 | Un error de 1–4px se lee como un error |
Botón de icono 16×16 con padding: 0 | Glifo de 16px, ::before { inset: -12px } para un objetivo de 40px | El objetivo del ratón es 40px |
.row + .row { margin-top: 8px } con hover y click en filas | .row { padding-block: 4px }, sin margin | Un espacio entre filas es una zona muerta y parpadea en hover |
.menu { width: 200px } | width: max-content; min-width: 160px; max-width: 320px | El menú se ajusta a su etiqueta más larga |
@media (max-width: 768px) en una tarjeta | @container (width < 400px) en el padre de la tarjeta | Una tarjeta en una barra lateral estrecha necesita su propio ancho, no el de la pantalla |
<img src> sin tamaño, luego una tarjeta que salta | width, height o aspect-ratio establecidos | La caja existe antes de los bytes |
| Encabezado fijo, ancla aterriza debajo de él | scroll-padding-block-start: 56px en el scroller | El foco y las anclas nunca se colocan debajo del chrome |
Antes de terminar
Busca en el diff estos patrones, luego verifica.
| Patrón | Arreglo |
|---|---|
margin, padding o gap con un valor que no está en la escala | Paso más cercano |
Un valor de gap usado dentro y entre grupos | Dentro ≤ la mitad de entre |
Botón solo icono de menos de 24px, o menos de 40px sin ::before | Expandir con un pseudo-elemento |
gap o margin entre filas o elementos de menú cliqueables | Padding dentro del elemento |
width fijo en un menú, botón o cuadro de texto | max-content, min-width, max-width; min-height para texto |
@media (max-width dentro de un componente reutilizable | @container en su padre |
height: 100vh para un cascarón | 100dvh |
Pista 1fr o hijo de flex que contiene texto largo | minmax(0, 1fr) o min-width: 0 |
margin-left, padding-right, left: 0, text-align: left en UI reflejada | Forma lógica |
Encabezado o pie fijo sin scroll-padding-block | Establecerlo a la altura del chrome |
| Alto de fila o ancho de columna del esqueleto diferente al cargado | Emparejarlos |
<img> sin dimensiones o aspect-ratio | Añadir uno |
Verifica con measure.md: renderiza a 2× a 390px y al ancho más ancho del cascarón, ejecuta el script, recorta cada candidato y cuenta píxeles. Luego navega con Tab por la pantalla con el chrome fijo en su lugar, y carga las cadenas más largas. Informa lo que no pudiste verificar como no verificado.