O que layout faz?
Este skill decide onde as coisas vão, a que distância ficam e qual o seu tamanho. Toma as mesmas decisões na mesma ordem e verifica o resultado contra os pixels renderizados.
Antes de qualquer decisão, aplique product-ui §1 (o diário de gosto e precedência) e §5 (qual skill é dona do quê).
Decidir nesta ordem
- Shell. Nomeie as regiões da tela: sidebar, toolbar, conteúdo, inspetor. Cada região é um proprietário de scroll ou nenhum. Detalhes e larguras: shell-and-responsive.md. Porquê: o shell fixa cada largura na qual o layout interno tem de viver.
- Largura e medida. Defina a coluna de conteúdo pelo que ela contém: formulário 440px, leitura 720px com prosa limitada à medida do
type(66ch), dados 1200px. Uma coluna nunca se estica porque a tela é larga. Porquê: a partir de 75 caracteres, uma linha perde o leitor, e um formulário esticado envia os olhos pela tela. - Espaço. Use a escala abaixo e mais nada. Agrupe primeiro, com espaço; adicione um preenchimento, depois uma linha, apenas onde o espaço não consegue carregá-lo. spacing-and-density.md.
- Alinhar. Escolha as arestas compartilhadas e coloque tudo nelas. Um desvio de 1–4px é um bug. Corrija opticamente onde a geometria parece errada. spacing-and-density.md.
- Tamanho. Escolha a densidade para a região, depois as áreas de toque. spacing-and-density.md.
- Pior caso de dados. Redimensione a caixa para o conteúdo mais longo, mais curto, ausente, traduzido e ainda a carregar antes de considerar pronto. stability-and-text.md.
- Adaptar. Faça a estrutura responder ao seu container, não ao dispositivo. shell-and-responsive.md.
- Medir. Renderize a 2×, meça gaps e arestas, corrija o que os pixels mostram. measure.md.
A escala
Uma escala, base 4px. Nunca escreva um valor de espaçamento que não seja um degrau. Porquê: valores únicos (10px, 14px, 18px) tornam o ritmo impossível de ver e impossível de manter.
| Token | px | Uso típico |
|---|---|---|
--space-xs | 4 | rótulo para sua descrição; ícone para rótulo num chip |
--space-sm | 8 | itens dentro de um grupo; padding block de controlo |
--space-md | 12 | entre controlos com borda; padding inline de controlo |
--space-lg | 16 | entre campos; padding de cartão compacto |
--space-20 | 20 | entre blocos dentro de um cartão |
--space-xl | 24 | entre grupos; padding de cartão confortável |
--space-2xl | 32 | entre secções |
--space-3xl | 48 | acima do primeiro título de uma página |
--space-4xl | 64 | abaixo do último bloco de uma página |
Ajustes de 1–2px para alinhamento ótico são os únicos valores fora da escala. Nomeie-os (--play-nudge: 2px).
Regras que não precisam de referência
- Agrupar com espaço. A quebra entre grupos é pelo menos 2× o gap dentro de um grupo: 8px dentro significa 16px ou mais entre. Espaçamento igual em todo o lado torna o agrupamento ambíguo, e ambiguidade aqui é um bug funcional.
- Remover linhas antes de as adicionar. Espaço primeiro, um preenchimento segundo, um divisor por último, e apenas para dados densos. Porquê: cada linha custa atenção pela mesma informação que o espaço dá gratuitamente.
- Espaço acima igual a espaço abaixo para qualquer coisa entre duas coisas. Porquê: uma coisa mais perto de um vizinho lê-se como sendo dele.
- Controlos devem parecer controlos. Um botão, link ou toggle tem uma forma, um sublinhado ou uma posição na toolbar. Texto estático nunca partilha esses atributos. Porquê: um controlo estilizado como texto esconde-se, e texto estilizado como controlo recolhe cliques mortos.
- Ações ficam em cima dos dados em que atuam, nunca por baixo. Um controlo que define algo fica ao lado dele.
- Conteúdo principal vai primeiro e à esquerda. Ordem visual igual à ordem do DOM: nunca use
order,row-reverseou posicionamento grid para reordenar conteúdo. Porquê: a ordem do teclado e do leitor de ecrã segue o DOM. - Propriedades lógicas (
margin-inline-start,padding-block,inset-inline-end,text-align: start) para tudo que se espelha. Porquê: RTL funciona assim sem uma segunda folha de estilos. - Nunca colocar controlos críticos na borda inferior de uma janela ou sidebar. Porquê: as pessoas empurram janelas parcialmente para fora do ecrã.
- Scroll mostra o que está escondido. Conteúdo além de uma aresta recebe uma dica visível: um vislumbre de 16–32px ou um fade.
- Nada se desloca. Hover, seleção e foco nunca mudam o tamanho de um elemento. Os dados carregados ocupam a caixa que o skeleton segurou.
Pares
| Quebrado | Corrigido | Porquê |
|---|---|---|
gap: 12px no formulário e gap: 12px entre os seus fieldsets | gap: 8px dentro, gap: 24px entre | Grupos precisam de uma quebra de pelo menos 2× |
padding: 10px 14px | padding: var(--space-sm) var(--space-md) | Apenas degraus |
| Título em x=24, descrição em x=26 | Ambos em x=24 | Um desvio de 1–4px lê-se como um erro |
Botão de ícone 16×16 com padding: 0 | Glyph 16px, ::before { inset: -12px } para um alvo de 40px | O alvo do rato é 40px |
.row + .row { margin-top: 8px } com hover e clique nas linhas | .row { padding-block: 4px }, sem margin | Um gap entre linhas é uma zona morta e faz piscar o hover |
.menu { width: 200px } | width: max-content; min-width: 160px; max-width: 320px | O menu abraça o seu rótulo mais longo |
@media (max-width: 768px) num cartão | @container (width < 400px) no pai do cartão | Um cartão numa sidebar estreita precisa da sua própria largura, não da do ecrã |
<img src> sem tamanho, depois um cartão que salta | width, height ou aspect-ratio definidos | A caixa existe antes dos bytes |
| Cabeçalho sticky, âncora aterrar por baixo dele | scroll-padding-block-start: 56px no contentor de scroll | Foco e âncoras nunca ficam por baixo do chrome |
Antes de terminar
Pesquise no diff por estes padrões e verifique.
| Padrão | Correção |
|---|---|
margin, padding ou gap com um valor não na escala | Degrau mais próximo |
Um valor gap usado dentro e entre grupos | Dentro ≤ metade do entre |
Botão só de ícone com menos de 24px, ou menos de 40px sem ::before | Expandir com um pseudo-elemento |
gap ou margin entre linhas clicáveis ou itens de menu | Padding dentro do item |
width fixo num menu, botão ou caixa de texto | max-content, min-width, max-width; min-height para texto |
@media (max-width dentro de um componente reutilizável | @container no seu pai |
height: 100vh para um shell | 100dvh |
Track 1fr ou flex child segurando texto longo | minmax(0, 1fr) ou min-width: 0 |
margin-left, padding-right, left: 0, text-align: left em UI espelhada | Forma lógica |
Cabeçalho ou rodapé sticky sem scroll-padding-block | Definir para a altura do chrome |
| Altura de linha de skeleton ou larguras de coluna diferentes das carregadas | Corresponder |
<img> sem dimensões ou aspect-ratio | Adicionar uma |
Verifique com measure.md: renderize a 2× a 390px e na largura mais larga do shell, execute o script, recorte cada candidato e conte os pixels. Depois percorra com Tab o ecrã com chrome sticky no lugar, e carregue as strings mais longas. Relate o que não conseguiu verificar como "não verificado".