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.
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:
<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:
.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:
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:
.topo { grid-area: topo; }
.menu { grid-area: menu; }
.conteudo { grid-area: conteudo; }
.rodape { grid-area: rodape; }.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:
.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:
[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:
.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;
}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á:
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);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:
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);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:
@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 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:
@media (max-width: 700px) {
.painel {
grid-template-areas:
"topo"
"menu"
"conteudo"
"rodape";
}
}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:
.painel {
display: grid;
gap: 12px;
height: 600px;
grid-template:
"topo topo" 72px
"menu conteudo" 1fr
"rodape rodape" 56px
/ 220px 1fr;
}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:
.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;
}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:
.carregando {
grid-row: menu-start / conteudo-end;
grid-column: menu-start / conteudo-end;
background: rgba(0, 0, 0, 0.6);
}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:
.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];
}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:
.painel {
grid-template-areas:
"topo topo"
"2a-serie 2a-serie"
"rodape rodape";
}
.serie { grid-area: 2a-serie; }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.
Perguntas frequentes
Preciso declarar grid-template-columns junto com grid-template-areas?
Posso repetir o mesmo nome em linhas separadas do desenho?
O nome da área precisa aparecer também em algum lugar do HTML?
Trocar a ordem visual com areas atrapalha a acessibilidade?
Qual a diferença entre grid-template e grid?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- MDN — grid-template-areas — developer.mozilla.org
- CSS Grid Layout Module Level 2 — Named Areas — w3.org



