Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

grid-template-areas: desenhar o layout com nomes

Como montar header, sidebar, conteúdo e rodapé com um desenho em texto e reorganizar tudo no celular trocando só três linhas de CSS.

Rodolfo Mori12 min de leitura

grid-template-areas deixa você desenhar o layout dentro do próprio CSS: cada linha da grade vira uma string entre aspas, e cada palavra dentro da string é o nome de uma célula. Depois cada elemento diz em qual nome ele mora. O CSS passa a se parecer com a página.

Todos os exemplos aqui são o painel da secretaria da Escola Girassol: um cabeçalho, um menu lateral com as turmas, a área de conteúdo e um rodapé. As faixas de coluna e de linha continuam vindo de grid-template-columns e grid-template-rows, que são o assunto da lição CSS Grid: colunas, linhas e a unidade fr. O que muda aqui é como os elementos escolhem onde entrar.

Todas as medidas abaixo saíram de um Chromium 151 rodando em modo headless e são as caixas reais devolvidas por getBoundingClientRect(). A janela está em 1000×700, exceto onde eu digo a largura usada. Para você conseguir repetir os mesmos números, a página tem só três regras de base além do que aparece em cada exemplo: * { box-sizing: border-box }, body { margin: 0; font: 16px/1.4 system-ui, sans-serif } e .painel > * { padding: 8px }. Essa base é o que define a altura das faixas que o navegador dimensiona pelo conteúdo.

O desenho entre aspas: uma string por linha da grade

O HTML do painel é o mais comum possível, com as tags semânticas no lugar:

html
<div class="painel">
  <header class="topo">Escola Girassol</header>
  <nav class="menu"><a href="#turmas">Turmas</a></nav>
  <main class="conteudo"><a href="#matriculas">Matrículas</a></main>
  <footer class="rodape">Secretaria</footer>
</div>

Agora o desenho. Três strings, uma por linha da grade; duas palavras em cada string, uma por coluna:

css
.painel {
  display: grid;
  grid-template-columns: 220px 1fr;
  grid-template-rows: 72px 1fr 56px;
  grid-template-areas:
    "topo   topo"
    "menu   conteudo"
    "rodape rodape";
  height: 600px;
  gap: 12px;
}

Repetir topo nas duas colunas da primeira string é o jeito de dizer “esta área ocupa a linha inteira”. Os espaços a mais entre as palavras são só alinhamento visual — o navegador ignora. Medindo as caixas:

.topo x= 0 y= 0 220x72 .menu x= 232 y= 0 768x72 .conteudo x= 0 y= 84 220x448 .rodape x= 232 y= 84 768x448 --- gridTemplateAreas = "topo topo" "menu conteudo" "rodape rodape"

Nada foi para o lugar. O .menu está na primeira linha, ao lado do .topo; o .rodape está no meio da página. E, mesmo assim, o valor computado mostra que o desenho foi aceito.

É aqui que a maior parte das pessoas trava. O desenho sozinho não move ninguém: ele apenas dá nome às células. Como nenhum elemento reivindicou um nome, todos caíram por colocação automática, na ordem do HTML.

grid-area: o nome no item, senão o desenho não vale nada

A segunda metade da técnica são quatro linhas de CSS nos filhos:

css
.topo     { grid-area: topo; }
.menu     { grid-area: menu; }
.conteudo { grid-area: conteudo; }
.rodape   { grid-area: rodape; }
.topo x= 0 y= 0 1000x72 .menu x= 0 y= 84 220x448 .conteudo x= 232 y= 84 768x448 .rodape x= 0 y= 544 1000x56

.topo → {“gridArea”:“topo”,“gridRow”:“topo”,“gridColumn”:“topo”}

Agora sim: .topo ocupa os 1000px de largura, .menu fica com os 220px da coluna estreita e .rodape desceu para o fim. Repare no valor computado: um grid-area: topo preencheu ao mesmo tempo grid-row e grid-column. É um atalho de quatro propriedades (grid-row-start, grid-column-start, grid-row-end, grid-column-end) que, com um nome de área, dispara as quatro de uma vez.

O ponto é buraco, não é reserva

Uma célula que você quer deixar vazia se escreve com um ponto. Aqui o rodapé sai de baixo do menu e passa a acompanhar só a coluna de conteúdo:

css
.painel {
  grid-template-areas:
    "topo topo"
    "menu conteudo"
    ".    rodape";
}

Para testar o que acontece com a célula vazia, coloquei no fim do painel um <div class="intruso"> sem nenhum grid-area. E rodei a mesma medição também com ... no lugar do ponto:

[um ponto] computado = "topo topo" "menu conteudo" ". rodape" .rodape x= 232 y= 544 768x56 .intruso x= 0 y= 544 220x56

[tres pontos] computado = “topo topo” “menu conteudo” “. rodape” .rodape x= 232 y= 544 768x56 .intruso x= 0 y= 544 220x56

Duas lições numa saída só. Primeira: ... e . são a mesma coisa, e o navegador normaliza tudo para um ponto no valor computado — a sequência de pontos existe só para você alinhar as colunas no editor. Segunda, e mais importante: o .intruso foi parar exatamente dentro do buraco, em x=0 y=544.

O ponto marca uma célula sem nome, não uma célula reservada. Qualquer item sem grid-area continua elegível para cair ali pela colocação automática. Se você precisa de um espaço realmente intocado, o caminho é dimensionar a faixa, não desenhar um ponto.

Área precisa ser retângulo — e o navegador não avisa quando não é

Esta é a regra que mais custa tempo. Toda área nomeada tem que formar um retângulo cheio na grade. Um desenho em L é inválido — e o preço de errar é alto. Repare que a única mudança é a última string:

css
.painel {
  display: grid;
  grid-template-columns: 220px 1fr;
  grid-template-rows: 72px 1fr 56px;
  grid-template-areas:
    "topo   topo"
    "menu   conteudo"
    "topo   rodape";
  height: 600px;
  gap: 12px;
}
.topo x= 874 y= 562 126x38 .menu x= 874 y= 562 126x38 .conteudo x= 874 y= 562 126x38 .rodape x= 874 y= 562 126x38

grid-template-areas = none grid-template-columns = 220px 618.078px 0px 125.922px mensagens no console = 0

Os quatro elementos empilharam no mesmo ponto, no canto inferior direito. A propriedade computou none, como se você nunca tivesse escrito o desenho. E o grid-template-columns que você declarou com duas faixas apareceu com quatro: as duas últimas são colunas implícitas que o navegador criou porque grid-area: topo virou um nome de linha que não existia em lugar nenhum.

O número que importa é o último: zero mensagens no console. Nenhum aviso, nenhum erro. A página só fica errada.

E não adianta procurar a declaração no DevTools, porque ela não chegou lá:

js
const regra = [...document.styleSheets[0].cssRules]
  .find((r) => r.selectorText === '.painel');

const painel = document.querySelector('.painel');
painel.style.gridTemplateAreas = '"topo topo" "menu conteudo" "topo rodape"';

console.log('na folha de estilo:', JSON.stringify(regra.style.gridTemplateAreas));
console.log('no style inline:   ', JSON.stringify(painel.style.gridTemplateAreas));
console.log('computado:         ', getComputedStyle(painel).gridTemplateAreas);
na folha de estilo: "" no style inline: "" computado: none

O valor inválido não é guardado nem riscado: ele é jogado fora na hora da análise. No painel Styles do DevTools a linha simplesmente não aparece, o que faz muita gente concluir que “o CSS não carregou”.

O que uma ferramenta pega e o que ela deixa passar

Vale saber que nem um validador de CSS resolve isso sozinho. Rodei os quatro desenhos pelo css-tree, que compara o valor com a gramática oficial da propriedade:

js
import * as csstree from 'css-tree';

const valor = '"topo topo" "menu conteudo" rodape rodape';
const ast = csstree.parse(`grid-template-areas: ${valor}`, {
  context: 'declaration',
  positions: true,
});

const { error } = csstree.lexer.matchDeclaration(ast);
console.log(error.message);
console.log('coluna:', error.loc.start.column);
Mismatch syntax: none | <string>+ value: "topo topo""menu conteudo"rodape rodape ----------------------------------^ coluna: 50

A esquecida de aspas ele aponta com o cursor no caractere exato, e o error.loc ainda dá a coluna para você levar até o editor. Os outros dois erros, não:

desenho erro css-tree Chromium
"topo topo" "menu conteudo" "rodape rodape" nenhum casa com a gramática aplica
"topo topo" "menu conteudo" "topo rodape" área em L casa com a gramática none
"topo topo" "menu conteudo" "rodape" linha com menos colunas casa com a gramática none
"topo topo" "menu conteudo" rodape rodape faltou as aspas Mismatch, coluna 50 none

A gramática da propriedade é literalmente none | <string>+: uma ou mais strings. Contar colunas e conferir se cada nome forma um retângulo é uma regra semântica, que mora depois da gramática. Por isso o linter passa e o navegador reprova. Na prática, o teste confiável é um só: leia getComputedStyle(el).gridTemplateAreas no console e veja se voltou none.

Redesenhar a página inteira dentro de uma media query

Essa é a razão de existir do grid-template-areas. Para virar o painel da secretaria em uma coluna no celular, você reescreve o desenho — e mais nada:

css
@media (max-width: 700px) {
  .painel {
    grid-template-columns: 1fr;
    grid-template-rows: 72px 1fr auto 56px;
    grid-template-areas:
      "topo"
      "conteudo"
      "menu"
      "rodape";
  }
}

Repare que aproveitei para inverter menu e conteúdo: no celular, o menu de turmas vai para baixo. Medindo nas duas larguras e apertando Tab duas vezes em cada uma:

viewport 1000px .topo x= 0 y= 0 1000x72 .menu x= 0 y= 84 220x448 .conteudo x= 232 y= 84 768x448 .rodape x= 0 y= 544 1000x56 ordem no HTML: topo → menu → conteudo → rodape ordem do Tab: menu/Turmas → conteudo/Matrículas

viewport 420px .topo x= 0 y= 0 420x72 .menu x= 0 y= 494 420x38 .conteudo x= 0 y= 84 420x398 .rodape x= 0 y= 544 420x56 ordem no HTML: topo → menu → conteudo → rodape ordem do Tab: menu/Turmas → conteudo/Matrículas

O .menu desceu de y=84 para y=494, abaixo do conteúdo, sem uma única alteração no HTML. As media queries em si são o assunto da lição media queries no CSS.

Agora a parte que ninguém comenta: a ordem do Tab não mudou. Em 420px o menu aparece depois do conteúdo, mas quem navega pelo teclado ainda chega nele primeiro, porque a ordem de foco segue o HTML. Um leitor de tela lê na mesma ordem. Mover coisas com o desenho é ótimo para ajuste visual; quando a ordem lógica muda de verdade, quem precisa mudar é o HTML semântico.

O erro mais comum: trocar o desenho e esquecer as colunas

Este bug aparece toda semana. A media query redesenha para uma coluna, mas o grid-template-columns: 220px 1fr continua valendo, porque ninguém o sobrescreveu:

css
@media (max-width: 700px) {
  .painel {
    grid-template-areas:
      "topo"
      "menu"
      "conteudo"
      "rodape";
  }
}
.topo x= 0 y= 0 220x72 .menu x= 0 y= 84 220x398 .conteudo x= 0 y= 494 220x56 .rodape x= 0 y= 562 220x38 areas = "topo" "menu" "conteudo" "rodape" cols = 220px 188px rows = 72px 397.609px 56px 38.3906px

O desenho é válido, então nada colapsa — o que talvez seja pior. Numa tela de 420px, todo o painel se espremeu em 220px e sobraram 188px de nada à direita. As alturas também saíram do lugar: o desenho pede quatro linhas e o grid-template-rows só declarou três, então a quarta virou faixa implícita dimensionada pelo conteúdo.

Regra de bolso: quando o desenho muda de número de colunas ou de linhas, grid-template-columns e grid-template-rows mudam junto, na mesma media query.

grid-template: colunas, linhas e o desenho numa propriedade só

Existe um atalho que junta as três propriedades e coloca o tamanho de cada faixa ao lado da string correspondente. As colunas vêm depois de uma barra:

css
.painel {
  display: grid;
  gap: 12px;
  height: 600px;
  grid-template:
    "topo   topo"     72px
    "menu   conteudo" 1fr
    "rodape rodape"   56px
    / 220px 1fr;
}
.topo x= 0 y= 0 1000x72 .menu x= 0 y= 84 220x448 .conteudo x= 232 y= 84 768x448 .rodape x= 0 y= 544 1000x56

grid-template-areas = “topo topo” “menu conteudo” “rodape rodape” grid-template-columns = 220px 768px grid-template-rows = 72px 448px 56px

Pixel por pixel, o mesmo resultado da forma longa. A vantagem é ler a altura de cada faixa na mesma linha do desenho dela; a desvantagem é que a media query precisa reescrever o bloco inteiro, e não só o desenho.

Existe também o atalho grid, que parece igual e não é. Rodei os dois em um painel que já tinha grid-auto-rows: 100px declarado antes:

css
.painel {
  display: grid;
  grid-auto-rows: 100px;
  /* a segunda medição trocou grid-template por grid, sem mudar mais nada */
  grid-template: "topo topo" 72px "menu conteudo" 1fr "rodape rodape" 56px / 220px 1fr;
}
grid-template → grid-auto-rows: 100px grid → grid-auto-rows: auto

O atalho grid também zera grid-auto-flow, grid-auto-rows e grid-auto-columns. Se você configurou faixas implícitas antes, trocar grid-template por grid apaga esse trabalho em silêncio.

Todo nome de área vira nome de linha de graça

Ao nomear uma área menu, o navegador cria quatro nomes de linha para você: menu-start e menu-end em cada eixo. Dá para usar isso sem declarar nada.

No painel da secretaria, uma tela de “carregando” precisa cobrir o menu e o conteúdo, mas não o cabeçalho nem o rodapé. Sem nome de linha, você contaria faixas na mão. Com nome de linha:

css
.carregando {
  grid-row: menu-start / conteudo-end;
  grid-column: menu-start / conteudo-end;
  background: rgba(0, 0, 0, 0.6);
}
.menu x= 0 y= 84 220x448 .conteudo x= 232 y= 84 768x448 .carregando x= 0 y= 84 1000x448

A camada de carregamento pegou exatamente a união das duas áreas: começa em x=0 como o menu e termina onde o conteúdo termina. Se amanhã o menu mudar de largura, a cobertura acompanha, porque o que está escrito é a intenção e não uma coordenada.

O caminho inverso também funciona, e é o melhor jeito de entender que as duas coisas são a mesma por baixo. Aqui não existe grid-template-areas nenhum — só linhas nomeadas:

css
.painel {
  display: grid;
  gap: 12px;
  height: 600px;
  grid-template-columns:
    [topo-start rodape-start menu-start] 220px
    [menu-end conteudo-start] 1fr
    [conteudo-end topo-end rodape-end];
  grid-template-rows:
    [topo-start] 72px
    [topo-end menu-start conteudo-start] 1fr
    [menu-end conteudo-end rodape-start] 56px
    [rodape-end];
}
.topo x= 0 y= 0 1000x72 .menu x= 0 y= 84 220x448 .conteudo x= 232 y= 84 768x448 .rodape x= 0 y= 544 1000x56 gridTemplateAreas = none

Mesmas quatro caixas, mesmos pixels, e grid-template-areas computando none. Os grid-area: topo dos filhos continuaram funcionando porque um par nome-start / nome-end nos dois eixos já define uma área. A propriedade grid-template-areas é, em essência, uma forma legível de escrever esses nomes.

Quando o desenho atrapalha em vez de ajudar

Nem todo layout melhora com nome de área. Três situações em que ele é a escolha errada:

Quando a quantidade de itens é desconhecida. Uma lista de turmas vinda do banco pode ter 3 ou 30 cartões, e você não vai nomear cada um. Aí o certo é auto-fit com minmax, que distribui sem nomear ninguém.

Quando o alinhamento é de uma dimensão só. Uma barra com logo à esquerda e dois botões à direita é uma linha, e resolve melhor com Flexbox. Se você está em dúvida no caso concreto, a comparação Flexbox ou Grid separa os dois por tipo de layout.

Quando o nome não pode virar identificador. Esta é a armadilha silenciosa. O desenho aceita qualquer palavra entre aspas, mas grid-area exige um identificador CSS válido — e identificador não começa com dígito. Aqui a linha do meio do painel virou uma <section class="serie"> só, no lugar do menu e do conteúdo:

css
.painel {
  grid-template-areas:
    "topo     topo"
    "2a-serie 2a-serie"
    "rodape   rodape";
}
.serie { grid-area: 2a-serie; }
gridTemplateAreas = "topo topo" "2a-serie 2a-serie" "rodape rodape" .serie grid-area = auto .topo x= 0 y= 0 1000x72 .serie x= 0 y= 84 220x448 .rodape x= 0 y= 544 1000x56

O desenho foi aceito inteiro, com o 2a-serie dentro. O que caiu foi o grid-area do filho: computou auto, e a seção foi colocada automaticamente na primeira célula livre, com 220px em vez da linha toda. De novo, sem uma linha no console. O mesmo vale para span, que é palavra reservada da propriedade.

Use nomes que começam com letra, sem acento e sem espaço: serie-2, turmas, avisos. E prefira nomes de papel (menu, conteudo) a nomes de posição (esquerda, topo-direita) — a posição muda na media query, o papel não.

O próximo desenho

Pegue um layout que você já escreveu com position ou com margens e reescreva o esqueleto dele em quatro strings. Depois abra o console, rode getComputedStyle($0).gridTemplateAreas com o contêiner selecionado e confirme que não voltou none — esse é o teste que economiza a tarde inteira.

Na sequência, a trilha de CSS segue para grades que se adaptam sozinhas, sem media query. E se quiser ver onde o Grid entra no plano completo, o guia de CSS mostra a ordem de estudo inteira.

Prefere aprender em vídeo?

Tem uma aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • css
  • grid
  • grid-template-areas
  • layout
  • responsivo

Perguntas frequentes

Preciso declarar grid-template-columns junto com grid-template-areas?
Não é obrigatório, mas quase sempre você quer. Sem as colunas declaradas, cada coluna do desenho vira uma faixa auto e o tamanho passa a depender do conteúdo. E cuidado com o contrário: desenho com uma coluna e grid-template-columns com duas faixas deixa metade da tela vazia.
Posso repetir o mesmo nome em linhas separadas do desenho?
Só se as células formarem um retângulo cheio. Duas aparições separadas por outra área não formam retângulo, e o navegador descarta a declaração inteira em silêncio. Use nomes diferentes ou reorganize o desenho.
O nome da área precisa aparecer também em algum lugar do HTML?
Não. O nome existe só dentro do CSS: a string define, e o grid-area do elemento consome. Nenhuma classe, id ou atributo do HTML precisa combinar com ele.
Trocar a ordem visual com areas atrapalha a acessibilidade?
Pode atrapalhar. A ordem de leitura de um leitor de tela e a ordem do Tab seguem o HTML, não o desenho. Se o menu aparece embaixo mas continua sendo o segundo do HTML, o Tab passa por ele antes do conteúdo. Mude o HTML quando a ordem lógica realmente for outra.
Qual a diferença entre grid-template e grid?
grid-template escreve colunas, linhas e áreas de uma vez. grid faz o mesmo e ainda zera grid-auto-flow, grid-auto-rows e grid-auto-columns. Se você tinha grid-auto-rows configurado, o atalho grid apaga esse valor.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Chromium 151.0.7922.34 (headless, Playwright 1.62.1) e css-tree 3.2.1, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — grid-template-areas — developer.mozilla.org
  2. CSS Grid Layout Module Level 2 — Named Areas — w3.org

Continue por aqui