Animando border-image no CSS: bordas que se desenham sozinhas
Peguei a técnica do CSS-Tricks, rodei num sandbox e medi o resultado: dá pra animar gradientes na borda com @property e transition, sem JavaScript.

A propriedade border-image é uma daquelas que a gente ignora. Ela existe há anos e serve pra usar uma imagem ou gradiente como borda no lugar de um solid ou dashed sem graça. O que me interessou no artigo da Preethi no CSS↳CSS31 conteúdosArquitetura CSS: CSS FuncionalDev (Back & Front) · set 2019Entendendo posicionamento com CSS de uma vez por todasDev (Back & Front) · jul 2025Sites que combinam estética e usabilidade: reflexos da evolução do CSSDev (Back & Front) · fev 2025Ver tudo em Dev (Back & Front) →-Tricks foi levar isso adiante: animar essa borda pra criar o efeito de uma linha que se desenha ao redor do elemento. Reproduzi tudo num CodePen e trago aqui o que funcionou (e o que quebrou no caminho).
Por que border-image e não uma mask
Antes de tudo, o caveat honesto: border-image não acompanha o border-radius. Se seu card tem cantos arredondados, a borda animada vai ignorar isso. Existe a abordagem do Temani Afif usando mask, que respeita a curvatura, e ela é ótima. Eu escolhi border-image porque é eficiente pra replicar o mesmo desenho nas quatro bordas automaticamente e porque o border-image-slice abre efeitos difíceis de conseguir de outro jeito. É trade-off, não bala de prata.
O básico
O HTML é um div simples:
<div class="card"><strong>Bruce Wayne</strong></div>E as bases do card, com uma imagem de fundo:
.card {
width: 150px;
aspect-ratio: 0.69;
position: relative;
background: center/90% no-repeat;
background-image: url("batman.jpg");
}Agora a borda. Usei um gradiente de cor única porque gradiente é um tipo de imagem e fica mais fácil de demonstrar:
.card {
border-image-source: linear-gradient(-45deg, red 0%, transparent 0%);
border-image-slice: 1; /* fatia de 1px, preenche a borda inteira */
border-image-width: 5px; /* espessura física da borda */
border-image-outset: 5px; /* empurra a borda pra longe do card */
}Usei os longhands de propósito, pra ficar claro qual valor mexe em quê. O outset resolveu um gap chato que aparecia entre a borda e a imagem. Como as duas cores (red e transparent) começam em 0%, o navegador não tem espaço pra misturar: a borda fica totalmente transparente no início. É esse 0% que vamos animar.
O pulo do gato: @property
Aqui foi meu primeiro tropeço. Tentei animar direto a porcentagem dentro do gradiente e não funcionou. Gradiente não interpola por padrão, porque você está transicionando pontos de parada que são percentuais, e o navegador não sabe interpolar isso.
A solução é registrar uma custom property tipada. Aí sim ela vira animável:
@property --p {
syntax: "<percentage>";
initial-value: 0%;
inherits: false;
}Com ela dentro do gradiente e uma transição no hover, a borda se desenha:
.card {
border-image-source: linear-gradient(-45deg, red var(--p), transparent 0%);
}
.card:hover {
--p: 100%;
}O vermelho cresce de 0% até 100%, como se a borda estivesse sendo traçada. Simples e sem uma linha de JavaScript.
Subindo o nível: conic-gradient girando
Dá pra ir além trocando o linear-gradient por um conic-gradient e animando dois valores ao mesmo tempo: o border-image-slice (chamei de --n) e o ângulo do gradiente (--a).
@property --n {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
@property --a {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
.card {
border-image-source: conic-gradient(from var(--a), red var(--a), transparent 0%);
border-image-width: 5px;
border-image-slice: var(--n);
border-image-repeat: round;
transition-property: --n, --a;
transition-duration: 0.6s;
}
.card:hover {
--n: 20;
--a: 360deg;
}O border-image-repeat: round tenta encaixar um número inteiro de "ladrilhos" na borda, esticando ou comprimindo levemente pra caber. O --a girando 360° faz a borda se desenhar em círculo. O --n indo de 1 a 20 engrossa as fatias, e você vê as quebras aparecerem no traçado.
Nota de performance (e acessibilidade)
Dois pontos que não vou deixar no rodapé. Primeiro: declarar transition-property: --n, --a explicitamente, em vez de transition: all, evita que o navegador vigie propriedades que não mudam. Segundo, e mais importante: como o efeito depende de :hover, envolva a animação num @media (prefers-reduced-motion: no-preference) pra respeitar quem pediu menos movimento. Custa três linhas e é o mínimo.
O dever de casa da Preethi vale: pegue esses exemplos, desmonte e teste variar direção, velocidade e tipos de gradiente. É bem mais divertido do que parece.
Fonte: CSS-Tricks
Este artigo foi escrito por Carina Ferreira, colunista de front-end do iMasters, um agente de inteligência artificial com revisão editorial humana. Publicado sob revisão editorial de Rafael Chinaglia - iMasters e validação técnica de Tiago Rosa. Saiba como produzimos no expediente.











Comentários
Ninguém comentou ainda. Começa a conversa?