---
id: intrinsic-css-layout
domain: web-craft
agents: [ux-design-expert, dev]
when: "ao montar layouts CSS resilientes e responsivos sem media queries frágeis"
---

# Intrinsic CSS Layout — os primitives de Every Layout

Baseado em **Every Layout** (Heydon Pickering & Andy Bell). A tese central: pare de
"desenhar telas" em breakpoints fixos e passe a **compor primitivos de layout** que se adaptam
ao conteúdo e ao espaço disponível por conta própria. O navegador já sabe fazer layout — o
trabalho é **declarar restrições, não posições**. Layout intrínseco = o componente decide como
se arranjar com base no seu próprio conteúdo e no espaço do container, não em quanto mede a
viewport.

Isto casa direto com o pack `anti-ai-look`: layout editorial, ritmo de espaço com contraste e
composição que respira são justamente o que os primitives entregam **sem** cair no grid-de-3-cards
default.

## O problema

Layout "feito por IA / por template" é, na prática, layout **extrínseco**: tudo amarrado a
breakpoints de pixel (`@media (max-width: 768px)`), larguras fixas, e o mesmo `gap-8` em tudo.
Os tells técnicos:

1. **Media queries baseadas na viewport** para componentes que deveriam reagir ao *seu* container.
   O card quebra a 768px mesmo dentro de uma sidebar de 300px — porque olha a tela, não a si mesmo.
2. **Espaçamento aplicado item a item** (`margin-bottom` em cada elemento, ou `<br>`/divs vazias)
   em vez de um ritmo declarado uma vez no container.
3. **Larguras fixas em px** que estouram em telas estreitas ou criam linhas de texto longas demais
   (medida ruim) em telas largas.
4. **Centralização por hack** (`margin-left/right` mágicos, `position: absolute` com `left: 50%`
   sem `transform`, flex aninhado desnecessário).
5. **Grid rígido de N colunas** que precisa de media query para virar 2, depois 1 — em vez de
   "encaixe quantas couberem".
6. **Tudo no mesmo múltiplo de 8** — espaçamento uniforme, sem tensão, robótico.

A consequência: layout que parece o mesmo template em qualquer projeto, frágil em conteúdo real
(título com 1 ou 7 palavras, lista com 2 ou 40 itens) e cheio de `@media` remendado.

## O conhecimento

### Fundamento 1 — A "lobotomized owl" (`* + *`) para ritmo

O seletor `* + *` ("coruja lobotomizada") aplica margem **só entre** irmãos adjacentes — nunca no
primeiro nem no último. É a base do espaçamento intrínseco: você declara o ritmo **uma vez** no
container e ele vale para qualquer quantidade de filhos.

```css
.stack > * + * {
  margin-block-start: var(--space, 1.5rem);
}
```

`margin-block-start` (não `margin-top`) respeita modo de escrita / direção. Nada de margem no
primeiro filho → sem colapso de margem indesejado com o container.

### Fundamento 2 — Espaçamento fluido com `clamp()`

Em vez de trocar tamanhos em breakpoints, escale **continuamente** entre um mínimo e um máximo,
com a viewport como variável no meio:

```css
:root {
  /* clamp(MÍNIMO, PREFERIDO-fluido, MÁXIMO) */
  --space-s:  clamp(0.75rem, 0.69rem + 0.3vw, 0.94rem);
  --space-m:  clamp(1.13rem, 1.04rem + 0.45vw, 1.41rem);
  --space-l:  clamp(1.5rem,  1.38rem + 0.6vw,  1.88rem);
  --space-xl: clamp(2.25rem, 2.07rem + 0.9vw,  2.81rem);

  --step-0: clamp(1rem, 0.95rem + 0.25vw, 1.13rem);   /* corpo */
  --step-2: clamp(1.41rem, 1.24rem + 0.85vw, 1.91rem); /* h3 */
  --step-4: clamp(1.95rem, 1.56rem + 1.95vw, 3.05rem); /* h1 */
}
```

A escala fluida resolve o tell de espaçamento uniforme: defina poucos passos (`s`/`m`/`l`/`xl`)
com **contraste real** entre os extremos e use `--space` para parametrizar cada primitive.

---

### Os primitives

Cada primitive é uma classe utilitária componível, parametrizada por custom properties. Não há
media queries — a adaptação é intrínseca.

#### 1. Stack — ritmo vertical

Injeta espaço **entre** elementos empilhados. O bloco mais usado da web.

```css
.stack {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}
.stack > * + * {
  margin-block-start: var(--space, 1.5rem);
}
/* "split": empurra o que vier depois do 1º filho pro fim do container */
.stack > :nth-child(2) {
  margin-block-end: auto;
}
```

Variantes por escala e exceção local (sem reescrever o seletor):

```css
[class^='stack'] > * { margin-block: 0; }   /* reset de margens herdadas */
.stack-large > * + * { margin-block-start: 3rem; }
.stack-small > * + * { margin-block-start: 0.5rem; }

/* exceção: um espaço maior antes E depois de um elemento específico */
.stack-exception,
.stack-exception + * { --space: 3rem; }
```

**Quando:** qualquer fluxo vertical — artigo, formulário, card. O `split` (`margin-block-end: auto`)
gruda um rodapé/CTA no fim de um card de altura variável.

#### 2. Box — a unidade com padding/borda coerentes

Caixa que **não vaza** em modo de alto contraste (a borda transparente vira visível em
`forced-colors`).

```css
.box {
  padding: var(--space, 1rem);
  border: var(--border-thin, 1px) solid;
  /* borda transparente vira visível em forced-colors: acessibilidade de graça */
  outline: var(--border-thin, 1px) solid transparent;
  outline-offset: calc(var(--border-thin, 1px) * -1);
  color: var(--color-light, #fff);
  background-color: var(--color-dark, #000);
}
.box * { color: inherit; }
/* inversão herda cor/fundo sem hardcode */
.box.invert {
  color: var(--color-dark, #000);
  background-color: var(--color-light, #fff);
}
```

**Quando:** card, alerta, qualquer "superfície". Padding fluido via `--space`. Evite raio uniforme
em tudo (ver `anti-ai-look`): varie por propósito.

#### 3. Center — centraliza e impõe a medida (measure)

Centraliza horizontalmente **e** limita a largura da linha de texto (`measure` ~60ch para leitura
confortável). Sem container artificial.

```css
.center {
  box-sizing: content-box;
  margin-inline: auto;
  max-inline-size: var(--measure, 60ch);
  padding-inline: var(--gutter, 1rem); /* respiro nas laterais */
}
/* variante: centraliza também filhos intrinsecamente menores que a measure */
.center-intrinsic {
  display: flex;
  flex-direction: column;
  align-items: center;
}
```

**Quando:** wrapper de conteúdo editorial/artigo. `--measure` controla o conforto de leitura; é
o antídoto pra linhas de 200 caracteres em telas largas.

#### 4. Cluster — agrupar coisas que quebram juntas

Para grupos de itens de tamanhos diferentes que devem **embrulhar** com espaçamento consistente:
tags, botões de ação, metadados, nav.

```css
.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space, 1rem);
  justify-content: var(--justify, flex-start);
  align-items: var(--align, center);
}
```

**Quando:** lista de tags, toolbar, grupo de botões, badges. `gap` cuida do espaço em qualquer
quebra de linha — nada de margens negativas modernas.

#### 5. Sidebar — duas colunas que viram empilhadas sozinhas (sem media query)

Conteúdo + barra lateral. A barra mantém uma largura ideal; quando o espaço aperta, **empilha
automaticamente** via `flex-wrap`. **CSS verbatim de Every Layout:**

```css
.with-sidebar {
  display: flex;
  flex-wrap: wrap;
  gap: 1em;
}
.sidebar {
  /* a menor entre 20ch e 33.333%, nunca passando de 100% */
  flex-basis: clamp(20ch, 33.333%, 100%);
  flex-grow: 1;
}
.with-sidebar > :last-child {
  flex-basis: 0;
  flex-grow: 999;          /* devora o espaço restante = vira "conteúdo principal" */
  min-inline-size: 50%;    /* abaixo disto, embrulha e empilha */
}
```

**Quando:** doc + índice, produto + filtros, conteúdo + aside. O `min-inline-size: 50%` é o gatilho
de quebra — ajuste pra controlar quando empilha. Coloque a `.sidebar` antes ou depois conforme a
ordem visual desejada.

#### 6. Switcher — horizontal vira vertical num limiar de container

Alterna entre linha e coluna baseado no **container** (não na viewport). **CSS verbatim:**

```css
.switcher {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space, 1rem);
  --threshold: 30rem; /* abaixo disto, empilha */
}
.switcher > * {
  flex-grow: 1;
  /* o truque: valor enorme (empilha) quando container < threshold;
     negativo/inválido (lado a lado) quando >= threshold */
  flex-basis: calc((var(--threshold) - 100%) * 999);
}
/* quantity query: 5+ itens forçam vertical (não espreme demais) */
.switcher > :nth-last-child(n+5),
.switcher > :nth-last-child(n+5) ~ * {
  flex-basis: 100%;
}
```

**Quando:** 2–4 blocos de peso igual (features, passos, colunas de preço) que devem ir lado a lado
quando cabe e empilhar quando não. Diferente da Sidebar, aqui os itens têm **peso simétrico**.

#### 7. Cover — herói de altura mínima com conteúdo centralizado

Ocupa pelo menos a altura da viewport, centra o conteúdo principal vertical e acomoda
cabeçalho/rodapé opcionais.

```css
.cover {
  display: flex;
  flex-direction: column;
  min-block-size: 100vh;          /* prefira 100svh em mobile p/ evitar pulo de barra */
  padding: var(--space, 1rem);
}
.cover > * {
  margin-block: var(--space, 1rem);
}
/* o elemento principal centraliza-se "empurrando" com auto dos dois lados */
.cover > .principal {
  margin-block: auto;
}
/* header/footer colam nas pontas */
.cover > :first-child:not(.principal) { margin-block-start: 0; }
.cover > :last-child:not(.principal)  { margin-block-end: 0; }
```

**Quando:** hero de landing, splash, tela de login. Use `100svh`/`100dvh` em vez de `100vh` para
não pular com a barra do browser mobile.

#### 8. Grid — quantas colunas couberem, sem media query

O grid auto-responsivo canônico. As colunas se ajustam ao espaço; o `min()` evita estouro em telas
estreitíssimas.

```css
.grid {
  display: grid;
  gap: var(--space, 1rem);
  /* encaixa quantas colunas de >= --min couberem; cada uma estica até 1fr */
  grid-template-columns: repeat(auto-fit, minmax(min(var(--min, 250px), 100%), 1fr));
  /* alinha alturas e nivela linhas */
  align-content: start;
}
```

**Quando:** galeria, lista de cards, dashboard. `auto-fit` colapsa trilhas vazias (cards esticam pra
preencher); troque por `auto-fill` se quiser trilhas vazias preservadas. `min(var(--min), 100%)` é o
detalhe que impede a barra de rolagem horizontal em telas menores que `--min`.

#### 9. Frame — proporção fixa que recorta o conteúdo

Mantém uma razão de aspecto (16:9, 1:1) e recorta a imagem/vídeo dentro com `object-fit: cover`.

```css
.frame {
  aspect-ratio: var(--n, 16) / var(--d, 9);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.frame > img,
.frame > video {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}
```

**Quando:** thumbnail, capa de card, embed de vídeo. `aspect-ratio` é suporte universal hoje — só
use o velho hack `padding-bottom: 56.25%` se precisar de browsers antigos.

#### 10. Reel — lista que rola na horizontal (carrossel honesto)

Rolagem horizontal com snap, sem JS. Itens mantêm largura própria e transbordam para o lado.

```css
.reel {
  display: flex;
  gap: var(--space, 1rem);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  /* esconde a barra mas mantém rolagem por teclado/toque */
  scrollbar-width: thin;
  overscroll-behavior-inline: contain;
}
.reel > * {
  flex: 0 0 var(--item-width, auto);  /* não encolhe: preserva largura */
  scroll-snap-align: start;
}
.reel > img { block-size: 100%; flex-basis: auto; width: auto; }
```

**Quando:** galeria horizontal, "continue assistindo", chips roláveis. Garanta foco/teclado e
respeite `prefers-reduced-motion` ao animar o scroll.

#### 11. Imposter — sobrepor e centralizar sobre outro elemento

Centraliza um elemento sobre o pai (modal, badge, "esgotado" sobre um produto).

```css
.imposter {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
}
/* variante "contida": nunca ultrapassa o pai, rola se for grande */
.imposter.contain {
  --margin: 0px;
  overflow: auto;
  max-inline-size: calc(100% - (var(--margin, 0px) * 2));
  max-block-size: calc(100% - (var(--margin, 0px) * 2));
}
```

O pai precisa de `position: relative`. **Quando:** modal/dialog centralizado, selo sobre imagem,
tooltip. Para conteúdo realmente modal, prefira o elemento `<dialog>` nativo; o Imposter é pra
sobreposição puramente visual.

### Composição — o todo é maior que as partes

O ganho real é **aninhar** primitives, cada um cuidando de uma dimensão:

```html
<!-- Center impõe a measure → Stack dá ritmo vertical → Grid encaixa os cards -->
<div class="center" style="--measure: 70ch;">
  <div class="stack" style="--space: var(--space-xl);">
    <h1>Manifesto</h1>
    <p>…</p>
    <div class="grid" style="--min: 18rem; --space: var(--space-l);">
      <article class="box stack">…</article>
      <article class="box stack">…</article>
    </div>
  </div>
</div>
```

Nenhuma media query. Cada primitive resolve uma responsabilidade; o conteúdo dita a quebra. Use o
**contraste de `--space`** entre seções (uma `--space-xl`, outra `--space-s`) para criar o ritmo
editorial que o pack `anti-ai-look` exige — em vez do `gap-8` uniforme.

## Checklist

Antes de entregar um layout, qualquer "não" é um remendo a corrigir:

- [ ] O espaçamento vertical vem de **um** `* + *` no container, não de margens item a item?
- [ ] Há **zero** media queries de viewport para coisas que deveriam reagir ao container (usou
      Sidebar/Switcher/Grid intrínsecos)?
- [ ] O wrapper de leitura tem `max-inline-size` em `ch` (measure ~60–70ch), não largura em px?
- [ ] O grid usa `repeat(auto-fit, minmax(min(--min, 100%), 1fr))` — encaixa o que cabe sozinho?
- [ ] Usei `clamp()` para espaço/tipografia fluida em vez de trocar valores em breakpoints?
- [ ] As propriedades são lógicas (`margin-block`, `inline-size`, `inset-inline`) e não físicas?
- [ ] As seções têm **contraste de `--space`** (ritmo), não todas no mesmo múltiplo?
- [ ] Cover usa `100svh`/`100dvh` (não `100vh`) pra não pular em mobile?
- [ ] Reel respeita teclado/foco e `prefers-reduced-motion`?
- [ ] Box mantém a borda transparente via `outline` pra sobreviver a `forced-colors`?

## Tabela de decisão

| Preciso de… | Primitive | Parâmetro-chave |
|---|---|---|
| Ritmo entre elementos empilhados | **Stack** | `--space` (use a escala fluida) |
| Card / superfície com padding e borda coerentes | **Box** | `--space`, `.invert` |
| Limitar largura de leitura e centralizar | **Center** | `--measure` (~60–70ch) |
| Agrupar tags/botões que quebram juntos | **Cluster** | `--space`, `--justify` |
| Conteúdo + barra lateral que empilha sozinha | **Sidebar** | `flex-basis: clamp(...)`, `min-inline-size` |
| 2–4 blocos simétricos: lado a lado ou empilhados | **Switcher** | `--threshold` (30rem), quantity query |
| Hero de altura mínima com conteúdo centrado | **Cover** | `min-block-size: 100svh`, `margin-block: auto` |
| "Quantas colunas couberem" sem media query | **Grid** | `--min`, `auto-fit`/`auto-fill` |
| Proporção fixa recortando imagem/vídeo | **Frame** | `aspect-ratio`, `object-fit: cover` |
| Lista que rola na horizontal (carrossel) | **Reel** | `overflow-x: auto`, `scroll-snap` |
| Sobrepor/centralizar sobre outro elemento | **Imposter** | `position: absolute` + `translate(-50%,-50%)` |

**Sidebar vs Switcher:** Sidebar = pesos **assimétricos** (uma coluna fina fixa, uma elástica).
Switcher = pesos **simétricos** (todos iguais), tudo lado a lado ou tudo empilhado.

**`auto-fit` vs `auto-fill` no Grid:** `auto-fit` colapsa trilhas vazias e estica os itens
existentes; `auto-fill` mantém trilhas vazias (itens não esticam pra preencher sozinhos).
