Novo Livro!
Categorias
Projetos
CSS

CSS Gap Decorations

CSS Gap Decorations

Por muito tempo, quem trabalha com desenvolvimento web teve que recorrer a hacks com bordas e pseudo-elementos para estilizar os espaços (gaps) entre os itens de layouts como grid e flexbox. Hoje, com as CSS Gap Decorations totalmente suportadas no Chrome e no Edge a partir da versão 149, é possível estilizar esses espaços de forma muito mais simples e com bastante controle.

A equipe da plataforma web do Edge, na Microsoft, liderou o design e a padronização do recurso, e a implementação já está sendo lançada em todos os navegadores baseados em Chromium.

Para mais contexto, vale conferir uma prévia inicial das gap decorations, escrita por Patrick Brosset, e um mergulho completo sobre o lançamento estável, escrito por Javier Contreras Tenorio.

Este artigo apresenta o que mudou no recurso desde então.

Recapitulando as gap decorations

As gap decorations estendem a já conhecida propriedade CSS column-rule, que já funcionava em layout multi-coluna, e passam a funcionar também em layouts de grid e flexbox.

Além disso, agora também há suporte à propriedade row-rule, permitindo que as decorações apareçam nos dois eixos. E a sintaxe também foi estendida para oferecer muito mais controle sobre as decorações.

Patrick já fez um bom passo a passo do recurso, mas, desde então, algumas coisas mudaram, como renomeações de propriedades para deixar tudo mais claro, além de novas propriedades, definidas a partir do feedback recebido de desenvolvedores web durante os testes iniciais.

Aqui está um resumo das mudanças:

AntigaNovaO que essa mudança significa
row-rule-outset / column-rule-outsetrow-rule-inset / column-rule-inset”Outset” virou “inset”, e agora essas propriedades recolhem as linhas para dentro a partir das bordas da decoração.
Sem controle fino de onde row-rule-inset e column-rule-inset se aplicamNovas propriedades longhand: row-rule-inset-cap, row-rule-inset-junction, row-rule-inset-start, row-rule-inset-end, e as equivalentes column-rule-inset-cap, column-rule-inset-junction, column-rule-inset-start, column-rule-inset-endAgora é possível controlar as pontas externas (caps) e as interseções (junctions) de forma independente. Também dá para recolher o início e o fim das decorações de forma independente.
gap-rule-paint-orderrule-overlapEssa propriedade foi renomeada para maior clareza. Ela controla como as decorações se sobrepõem onde as linhas de linha e coluna se cruzam.
N/Arule-visibility-itemsEssa nova propriedade controla se as linhas aparecem ao lado de áreas vazias dentro de um container.

Suporte dos navegadores

As gap decorations estão disponíveis no Chrome e no Edge a partir da versão 149, além de outros navegadores baseados em Chromium que correspondam a essas versões.

Outros motores de navegador têm se mostrado receptivos e engajados nos fóruns de padronização, então, com o tempo, esse recurso deve se tornar interoperável.

Para quem quiser usar o recurso agora, vale considerar:

  • Se essas decorações forem tratadas como um progressive enhancement, ótimo! Basta começar a usar o recurso hoje mesmo, já que nada quebra em navegadores sem suporte.
  • Se as gap decorations forem indispensáveis, há um polyfill em desenvolvimento para navegadores que ainda não têm suporte.

Usando as gap decorations

Chega de teoria: é hora de expandir a demonstração que Patrick usou em seu artigo. Novas seções serão adicionadas, usando os recursos mais recentes das gap decorations ao longo do caminho.

Homepage de um site pessoal com fundo preto e texto branco, mostrando linhas brancas entre os posts do blog.
Antes (ao vivo, código-fonte)
Homepage de um site pessoal com fundo preto e texto branco, mostrando linhas brancas entre os posts do blog.
Depois (ao vivo, código-fonte)

Vamos começar com uma marcação HTML mínima:

<body>
<header>
<h1>My personal site</h1>
<p class="tagline">...</p>
</header>
<nav>
<ul>
<li><a href="#home">Home</a></li>
<!-- etc. -->
</ul>
</nav>
<section id="home" class="home">
<div class="greeting">
<p class="lead">...</p>
</div>
<div class="photo">
<img src="cat.jpg" alt="A sleeping cat.">
</div>
<aside class="now">
<h3>Currently</h3>
<ul>
<li>Lorem <span>— ipsum dolor sit amet</span></li>
<!-- etc. -->
</ul>
</aside>
<div class="note">
<p>...</p>
</div>
<div class="photo">
<img src="tree.jpg" alt="An old olive tree trunk.">
</div>
</section>
<section id="blog" class="blog">
<h2>Blog</h2>
<div class="posts">
<article class="post">
<span class="date">June 2025</span>
<h3>Lorem ipsum dolor</h3>
<p>...</p>
</article>
<!-- rest of articles -->
</div>
</section>
<section id="about" class="about">
<h2>About</h2>
<div class="bio">
<img class="avatar" src="bike.jpg" alt="A bicycle leaning against a post.">
<div class="text">
<p>...</p>
<!-- etc. -->
</div>
</div>
</section>
<section id="links" class="links">
<h2>Elsewhere</h2>
<ul>
<li><a href="#">GitHub</a></li>
<!-- etc. -->
</ul>
</section>
<footer>
<p>&copy; 2025 My personal site.</p>
</footer>
</body>

E aqui está o CSS inicial. A página é um grande grid, com linhas de linha (row rules) atuando como divisores de seção. A área de navegação é um layout flexbox, com linhas de coluna tracejadas.

body {
display: grid;
gap: 4rem;
margin: 2rem;
row-rule:
1rem solid #efefef,
repeat(2, 2px solid #efefef);
}
nav ul {
display: flex;
flex-wrap: wrap;
gap: 2rem;
column-rule: 2px dashed #666;
}

O restante do artigo constrói a partir daqui.

Passo 1: suportar qualquer número de seções com repeat(auto)

No CSS inicial, o elemento body usa repeat(2, 2px solid #efefef) porque o site de partida tinha três seções estáticas. Como novas seções estão sendo adicionadas, o ideal é que o site se adapte automaticamente caso mais seções sejam incluídas no futuro.

Por isso, o número fixo de repetições será trocado por um repetidor auto. Assim, a página pode crescer e as linhas divisórias de seção acompanham esse crescimento:

body {
/* ... */
row-rule:
1rem solid #efefef,
repeat(auto, 2px solid #efefef);
}

Um layout simples com quatro seções de largura total empilhadas verticalmente.

Passo 2: decorar os espaços do flexbox na seção inicial (home)

Para a seção inicial (home), teremos um container flexbox com quebra de linha (wrap), contendo texto e imagens. O elemento com classe .home define o layout flexbox, e os diversos elementos dentro dele usam diferentes valores de flex-basis:

.home {
display: flex;
flex-wrap: wrap;
gap: 3rem;
}
.home > * {
flex: 1 1 12.5rem;
}
.home .greeting {
flex-basis: 26.25rem;
}
.home .note {
flex-basis: 22.5rem;
}

Aqui está a seção home, sem decorações:

Dois cards de post com texto à direita e imagem à esquerda, empilhados verticalmente, onde o post de baixo é mais largo que o de cima.

Agora, para adicionar um pouco da mágica das gap decorations, usaremos as propriedades row-rule e column-rule:

.home {
/* ... */
row-rule: 2px solid #999;
column-rule: 2px solid #999;
}

Aliás, como as linhas horizontais e verticais usam o mesmo valor, dá para condensá-las no novo shorthand rule e escrever uma única linha de código:

.home {
/* ... */
rule: 2px solid #999;
}

O resultado é este:

Dois cards de post com texto à direita e imagem à esquerda, empilhados verticalmente, onde o post de baixo é mais largo que o de cima. Linhas brancas separam as colunas e linhas.

Por padrão, as linhas de coluna ocupam toda a altura de cada linha do flexbox. Como há um gap de 3rem, as duas linhas do flexbox ficam separadas. O objetivo aqui é fazer as linhas de coluna se encontrarem com a linha de linha (row rule).

Para isso, será usada a propriedade column-rule-inset com o valor overlap-join. Esse valor une as linhas adjacentes nas junções, mantendo as pontas externas (os caps) niveladas em 0.

.home {
/* ... */
column-rule-inset: overlap-join;
}

Dois cards de post com texto à direita e imagem à esquerda, empilhados verticalmente, onde o post de baixo é mais largo que o de cima. Linhas brancas separam as colunas e linhas e se unem perfeitamente.

Passo 3: decorar itens de grid que ocupam múltiplas colunas ou linhas na seção do blog

Para a seção Blog, será usado um grid, permitindo que posts específicos ocupem múltiplas colunas ou linhas para dar destaque.

.posts {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 3rem;
}
.post.feature { grid-column: span 2; }
.post.tall { grid-row: span 2; }

Veja como fica a seção sem decorações:

Um grid de blog com uma primeira linha de três artigos contendo título e resumo. A segunda linha mostra dois posts com imagem no topo, título e resumo. O post à direita ocupa também a linha seguinte, que tem mais dois posts apenas com título e resumo. A última linha tem um único post com título e resumo.

Assim como na seção home, vamos adicionar a mágica das gap decorations:

.posts {
/* ... */
rule: 2px solid #999;
}

O que resulta nisto:

Um grid de blog com uma série de oito posts ocupando diferentes colunas e linhas, com linhas brancas conectando as colunas e linhas.

Como as linhas que aparecem ao lado de células vazias do grid não ficam boas visualmente, elas serão removidas com a nova propriedade rule-visibility-items:

.posts {
/* ... */
rule-visibility-items: around;
}

Com esse valor, as gap decorations só serão desenhadas quando pelo menos um dos lados do gap tiver conteúdo, então o espaço entre duas células vazias na última linha fica limpo, em vez de exibir uma linha solta no vazio:

Um grid de blog com uma série de oito posts ocupando diferentes colunas e linhas, com linhas brancas conectando as colunas e linhas.

De forma parecida com o passo anterior, para deixar as junções entre as decorações desse grid mais organizadas, será usado novamente o valor overlap-join da propriedade rule-inset:

.posts {
/* ... */
rule-inset: overlap-join;
}

E aqui está o resultado:

Um grid de blog com uma série de oito posts ocupando diferentes colunas e linhas, com linhas brancas conectando as colunas e linhas.

Passo 4: recolher decorações na seção Sobre (About)

Para a seção Sobre, a ideia é ter uma foto circular lado a lado com o texto, separadas por uma linha. Mas também será explorada a nova possibilidade de reduzir o tamanho das gap decorations.

Primeiro, um container flexbox posiciona a foto e o texto da bio lado a lado:

.bio {
display: flex;
align-items: center;
gap: 2.5rem;
}

Isso gera o seguinte resultado, uma espécie de media card comum em diversos sites:

Um componente de card com fundo preto e texto branco. Uma imagem circular está à esquerda e um bloco de texto está à direita.

Agora, a gap decoration é aplicada:

.bio {
/* ... */
column-rule: 2px solid #999;
}

O que resulta em:

Um componente de card com fundo preto e texto branco. Uma imagem circular está à esquerda e um bloco de texto está à direita. Uma linha branca vertical separa os dois lados.

Desta vez, porém, para dar um pouco mais de respiro, a propriedade column-rule-inset será usada para reduzir o tamanho da linha separadora:

.bio {
/* ... */
column-rule-inset: 2.5rem;
}

E aqui está o resultado:

Um componente de card com fundo preto e texto branco. Uma imagem circular está à esquerda e um bloco de texto está à direita. Uma linha branca vertical separa os dois lados e é mais curta que a altura total do card.

Para a seção Links, como há bastante itens, eles serão organizados em um grid.

.links ul {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem 2.5rem;
}

Assim:

Um grid de quatro por três de links.

Vamos adicionar gap decorations, desta vez com aparências diferentes para as linhas de linha e de coluna:

.links ul {
/* ... */
row-rule: 1px solid #c9a96a;
column-rule: 3px solid #6a707a;
}

Um grid de quatro por três de links com linhas brancas separando as colunas e linhas.

Observando com atenção, as linhas de coluna mais grossas aparecem atrás das linhas de linha mais finas. Para controlar isso e fazer as linhas de coluna aparecerem na frente, será usada a nova propriedade rule-overlap:

.links ul {
/* ... */
rule-overlap: column-over-row;
}

Essa propriedade faz exatamente o que o nome sugere: permite escolher se as colunas devem ser pintadas sobre as linhas, ou as linhas sobre as colunas.

E o resultado é este:

Um grid de quatro por três de links com linhas brancas separando as colunas e linhas, onde as linhas verticais são mais curtas que as células.

Por fim, a intenção é deixar as linhas de coluna um pouco mais curtas que o grid, sem separá-las por linha. Para isso, entra em cena a propriedade column-rule-inset-cap, que controla as pontas das gap decorations que não são junções ou interseções:

.links ul {
/* ... */
column-rule-inset-cap: 1.75rem;
}

E aqui está o resultado:

Um grid de quatro por três de links com linhas brancas separando as colunas e linhas, onde as linhas verticais são mais curtas que as células.

Passo 6: juntando tudo

E pronto: a página está finalizada. Confira o exemplo ao vivo e o código-fonte:

O layout completo do site pessoal.

Saiba mais

Vale a pena experimentar as gap decorations na prática. A forma mais rápida é pelo playground interativo, mas também há muitas outras demonstrações. É só brincar com cada propriedade e ver os gaps reagirem em tempo real.

Outra boa forma de aprender é visitar a documentação do MDN (em desenvolvimento no momento da publicação deste artigo) para mais referências.

Agradecimentos

As gap decorations do CSS existem graças a um grupo de pessoas que se importa profundamente com a web, nas equipes do Edge, do Chrome e no CSS Working Group. Agradecimentos especiais a Javier Contreras Tenorio, Kevin Babbitt, Alison Maher, Ian Kilpatrick e Patrick Brosset.